3
0
mirror of https://github.com/Qortal/AT.git synced 2025-01-30 19:02:14 +00:00

Try to reduce byte arrays being created/copied wherever possible. Also bump to v1.3.7 due to interface changes.

This commit is contained in:
catbref 2020-08-12 14:07:56 +01:00
parent facb9c213f
commit 2884b586d6
3 changed files with 159 additions and 59 deletions

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.ciyam</groupId> <groupId>org.ciyam</groupId>
<artifactId>AT</artifactId> <artifactId>AT</artifactId>
<version>1.3.6</version> <version>1.3.7</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>
<skipTests>true</skipTests> <skipTests>true</skipTests>

View File

@ -3,7 +3,6 @@ package org.ciyam.at;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -65,8 +64,6 @@ public class MachineState {
public final short numUserStackPages; public final short numUserStackPages;
public final long minActivationAmount; public final long minActivationAmount;
private final byte[] headerBytes;
/** Constants set in effect */ /** Constants set in effect */
private final VersionedConstants constants; private final VersionedConstants constants;
@ -137,15 +134,10 @@ public class MachineState {
// Constructors // Constructors
/** For internal use when recreating a machine state */ /** For internal use when recreating a machine state. Leaves ByteBuffer position immediately after header. */
private MachineState(byte[] headerBytes, API api, AtLoggerFactory loggerFactory) { private MachineState(ByteBuffer byteBuffer) {
if (headerBytes.length != HEADER_LENGTH) if (byteBuffer.remaining() < HEADER_LENGTH)
throw new IllegalArgumentException("headerBytes length " + headerBytes.length + " incorrect, expected " + HEADER_LENGTH); throw new IllegalArgumentException("ByteBuffer too small (" + byteBuffer.remaining() + "), minimum " + HEADER_LENGTH);
this.headerBytes = headerBytes;
// Parsing header bytes
ByteBuffer byteBuffer = ByteBuffer.wrap(this.headerBytes);
this.version = byteBuffer.getShort(); this.version = byteBuffer.getShort();
if (this.version < 1) if (this.version < 1)
@ -174,8 +166,13 @@ public class MachineState {
throw new IllegalArgumentException("Number of user stack pages must be >= 0"); throw new IllegalArgumentException("Number of user stack pages must be >= 0");
this.minActivationAmount = byteBuffer.getLong(); this.minActivationAmount = byteBuffer.getLong();
if (this.minActivationAmount < 0)
throw new IllegalArgumentException("Minimum activation amount must be >= 0");
// Header OK - set up code and data buffers // Header OK
}
private void setupSegmentsAndStacks() {
this.codeByteBuffer = ByteBuffer.allocate(this.numCodePages * this.constants.CODE_PAGE_SIZE); this.codeByteBuffer = ByteBuffer.allocate(this.numCodePages * this.constants.CODE_PAGE_SIZE);
this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE); this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE);
@ -185,35 +182,29 @@ public class MachineState {
this.userStackByteBuffer = ByteBuffer.allocate(this.numUserStackPages * this.constants.USER_STACK_PAGE_SIZE); this.userStackByteBuffer = ByteBuffer.allocate(this.numUserStackPages * this.constants.USER_STACK_PAGE_SIZE);
this.userStackByteBuffer.position(this.userStackByteBuffer.limit()); // Downward-growing stack, so start at the end this.userStackByteBuffer.position(this.userStackByteBuffer.limit()); // Downward-growing stack, so start at the end
this.api = api;
this.currentBlockHeight = 0;
this.currentBalance = 0;
this.previousBalance = 0;
this.steps = 0;
this.loggerFactory = loggerFactory;
this.logger = loggerFactory.create(MachineState.class);
} }
/** For creating a new machine state */ /** For creating a new machine state */
public MachineState(API api, AtLoggerFactory loggerFactory, byte[] creationBytes) { public MachineState(API api, AtLoggerFactory loggerFactory, byte[] creationBytes) {
this(Arrays.copyOfRange(creationBytes, 0, HEADER_LENGTH), api, loggerFactory); this(ByteBuffer.wrap(creationBytes));
int expectedLength = HEADER_LENGTH + this.numCodePages * this.constants.CODE_PAGE_SIZE + this.numDataPages * this.constants.DATA_PAGE_SIZE; int expectedLength = HEADER_LENGTH + this.numCodePages * this.constants.CODE_PAGE_SIZE + this.numDataPages * this.constants.DATA_PAGE_SIZE;
if (creationBytes.length != expectedLength) if (creationBytes.length != expectedLength)
throw new IllegalArgumentException("Creation bytes length does not match header values"); throw new IllegalArgumentException("Creation bytes length does not match header values");
setupSegmentsAndStacks();
System.arraycopy(creationBytes, HEADER_LENGTH, this.codeByteBuffer.array(), 0, this.numCodePages * this.constants.CODE_PAGE_SIZE); System.arraycopy(creationBytes, HEADER_LENGTH, this.codeByteBuffer.array(), 0, this.numCodePages * this.constants.CODE_PAGE_SIZE);
System.arraycopy(creationBytes, HEADER_LENGTH + this.numCodePages * this.constants.CODE_PAGE_SIZE, this.dataByteBuffer.array(), 0, System.arraycopy(creationBytes, HEADER_LENGTH + this.numCodePages * this.constants.CODE_PAGE_SIZE, this.dataByteBuffer.array(), 0,
this.numDataPages * this.constants.DATA_PAGE_SIZE); this.numDataPages * this.constants.DATA_PAGE_SIZE);
commonFinalConstruction(); commonFinalConstruction(api, loggerFactory);
} }
/** For creating a new machine state - used in tests */ /** For creating a new machine state - used in tests */
public MachineState(API api, AtLoggerFactory loggerFactory, byte[] headerBytes, byte[] codeBytes, byte[] dataBytes) { public MachineState(API api, AtLoggerFactory loggerFactory, byte[] headerBytes, byte[] codeBytes, byte[] dataBytes) {
this(headerBytes, api, loggerFactory); this(ByteBuffer.wrap(headerBytes));
if (codeBytes.length > this.numCodePages * this.constants.CODE_PAGE_SIZE) if (codeBytes.length > this.numCodePages * this.constants.CODE_PAGE_SIZE)
throw new IllegalArgumentException("Number of code pages too small to hold code bytes"); throw new IllegalArgumentException("Number of code pages too small to hold code bytes");
@ -221,14 +212,25 @@ public class MachineState {
if (dataBytes.length > this.numDataPages * this.constants.DATA_PAGE_SIZE) if (dataBytes.length > this.numDataPages * this.constants.DATA_PAGE_SIZE)
throw new IllegalArgumentException("Number of data pages too small to hold data bytes"); throw new IllegalArgumentException("Number of data pages too small to hold data bytes");
setupSegmentsAndStacks();
System.arraycopy(codeBytes, 0, this.codeByteBuffer.array(), 0, codeBytes.length); System.arraycopy(codeBytes, 0, this.codeByteBuffer.array(), 0, codeBytes.length);
System.arraycopy(dataBytes, 0, this.dataByteBuffer.array(), 0, dataBytes.length); System.arraycopy(dataBytes, 0, this.dataByteBuffer.array(), 0, dataBytes.length);
commonFinalConstruction(); commonFinalConstruction(api, loggerFactory);
} }
private void commonFinalConstruction() { private void commonFinalConstruction(API api, AtLoggerFactory loggerFactory) {
this.api = api;
this.loggerFactory = loggerFactory;
this.logger = loggerFactory.create(MachineState.class);
this.currentBlockHeight = 0;
this.currentBalance = 0;
this.previousBalance = 0;
this.steps = 0;
this.programCounter = 0; this.programCounter = 0;
this.onStopAddress = 0; this.onStopAddress = 0;
this.onErrorAddress = null; this.onErrorAddress = null;
@ -443,33 +445,89 @@ public class MachineState {
return this.codeByteBuffer.array(); return this.codeByteBuffer.array();
} }
private static class NumericByteArrayOutputStream extends ByteArrayOutputStream {
public NumericByteArrayOutputStream(int capacity) {
super(capacity);
}
public void writeShort(short value) {
this.write((byte) (value >> 8));
this.write((byte) (value));
}
public void writeInt(int value) {
this.write((byte) (value >> 24));
this.write((byte) (value >> 16));
this.write((byte) (value >> 8));
this.write((byte) (value));
}
public void writeLong(long value) {
this.write((byte) (value >> 56));
this.write((byte) (value >> 48));
this.write((byte) (value >> 40));
this.write((byte) (value >> 32));
this.write((byte) (value >> 24));
this.write((byte) (value >> 16));
this.write((byte) (value >> 8));
this.write((byte) (value));
}
}
/** For serializing a machine state */ /** For serializing a machine state */
public byte[] toBytes() { public byte[] toBytes() {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); int capacity = HEADER_LENGTH
+ this.dataByteBuffer.capacity()
+ this.callStackByteBuffer.capacity()
+ this.userStackByteBuffer.capacity()
+ 7 * 4 // Misc ints like stack lengths, PC, PCS, flags, etc.
+ 10 * 8; // Misc longs like previous balance, frozen balance, A, B, etc.
NumericByteArrayOutputStream bytes = new NumericByteArrayOutputStream(capacity);
try { try {
// Header first // Header first
bytes.write(this.headerBytes);
// Version
bytes.writeShort(version);
// Reserved
bytes.writeShort((short) 0);
// Code length
bytes.writeShort(numCodePages);
// Data length
bytes.writeShort(numDataPages);
// Call stack length
bytes.writeShort(numCallStackPages);
// User stack length
bytes.writeShort(numUserStackPages);
// Minimum activation amount
bytes.writeLong(minActivationAmount);
// Data // Data
bytes.write(this.dataByteBuffer.array()); bytes.write(this.dataByteBuffer.array());
// Call stack length (32bit unsigned int) // Call stack length (32bit unsigned int)
int callStackLength = this.callStackByteBuffer.limit() - this.callStackByteBuffer.position(); int callStackLength = this.callStackByteBuffer.limit() - this.callStackByteBuffer.position();
bytes.write(toByteArray(callStackLength)); bytes.writeInt(callStackLength);
// Call stack (only the bytes actually in use) // Call stack (only the bytes actually in use)
bytes.write(this.callStackByteBuffer.array(), this.callStackByteBuffer.position(), callStackLength); bytes.write(this.callStackByteBuffer.array(), this.callStackByteBuffer.position(), callStackLength);
// User stack length (32bit unsigned int) // User stack length (32bit unsigned int)
int userStackLength = this.userStackByteBuffer.limit() - this.userStackByteBuffer.position(); int userStackLength = this.userStackByteBuffer.limit() - this.userStackByteBuffer.position();
bytes.write(toByteArray(userStackLength)); bytes.writeInt(userStackLength);
// User stack (only the bytes actually in use) // User stack (only the bytes actually in use)
bytes.write(this.userStackByteBuffer.array(), this.userStackByteBuffer.position(), userStackLength); bytes.write(this.userStackByteBuffer.array(), this.userStackByteBuffer.position(), userStackLength);
// Actual state // Actual state
bytes.write(toByteArray(this.programCounter)); bytes.writeInt(this.programCounter);
bytes.write(toByteArray(this.onStopAddress)); bytes.writeInt(this.onStopAddress);
bytes.write(toByteArray(this.previousBalance)); bytes.writeLong(this.previousBalance);
// Various flags // Various flags
Flags flags = new Flags(); Flags flags = new Flags();
@ -489,30 +547,30 @@ public class MachineState {
boolean hasNonZeroB = this.b1 != 0 || this.b2 != 0 || this.b3 != 0 || this.b4 != 0; boolean hasNonZeroB = this.b1 != 0 || this.b2 != 0 || this.b3 != 0 || this.b4 != 0;
flags.push(hasNonZeroB); flags.push(hasNonZeroB);
bytes.write(toByteArray(flags.intValue())); bytes.writeInt(flags.intValue());
// Optional flag-indicated extra info in same order as above // Optional flag-indicated extra info in same order as above
if (this.onErrorAddress != null) if (this.onErrorAddress != null)
bytes.write(toByteArray(this.onErrorAddress)); bytes.writeInt(this.onErrorAddress);
if (this.sleepUntilHeight != null) if (this.sleepUntilHeight != null)
bytes.write(toByteArray(this.sleepUntilHeight)); bytes.writeInt(this.sleepUntilHeight);
if (this.frozenBalance != null) if (this.frozenBalance != null)
bytes.write(toByteArray(this.frozenBalance)); bytes.writeLong(this.frozenBalance);
if (hasNonZeroA) { if (hasNonZeroA) {
bytes.write(toByteArray(this.a1)); bytes.writeLong(this.a1);
bytes.write(toByteArray(this.a2)); bytes.writeLong(this.a2);
bytes.write(toByteArray(this.a3)); bytes.writeLong(this.a3);
bytes.write(toByteArray(this.a4)); bytes.writeLong(this.a4);
} }
if (hasNonZeroB) { if (hasNonZeroB) {
bytes.write(toByteArray(this.b1)); bytes.writeLong(this.b1);
bytes.write(toByteArray(this.b2)); bytes.writeLong(this.b2);
bytes.write(toByteArray(this.b3)); bytes.writeLong(this.b3);
bytes.write(toByteArray(this.b4)); bytes.writeLong(this.b4);
} }
} catch (IOException e) { } catch (IOException e) {
return null; return null;
@ -525,14 +583,22 @@ public class MachineState {
public static MachineState fromBytes(API api, AtLoggerFactory loggerFactory, byte[] stateBytes, byte[] codeBytes) { public static MachineState fromBytes(API api, AtLoggerFactory loggerFactory, byte[] stateBytes, byte[] codeBytes) {
ByteBuffer byteBuffer = ByteBuffer.wrap(stateBytes); ByteBuffer byteBuffer = ByteBuffer.wrap(stateBytes);
byte[] headerBytes = new byte[HEADER_LENGTH]; MachineState state = new MachineState(byteBuffer);
byteBuffer.get(headerBytes);
MachineState state = new MachineState(headerBytes, api, loggerFactory); if (codeBytes.length != state.numCodePages * state.constants.CODE_PAGE_SIZE)
if (codeBytes.length != state.codeByteBuffer.capacity())
throw new IllegalStateException("Passed codeBytes does not match length in header"); throw new IllegalStateException("Passed codeBytes does not match length in header");
state.api = api;
state.loggerFactory = loggerFactory;
state.logger = loggerFactory.create(MachineState.class);
state.currentBlockHeight = 0;
state.currentBalance = 0;
state.previousBalance = 0;
state.steps = 0;
state.setupSegmentsAndStacks();
// Pull in code bytes // Pull in code bytes
System.arraycopy(codeBytes, 0, state.codeByteBuffer.array(), 0, codeBytes.length); System.arraycopy(codeBytes, 0, state.codeByteBuffer.array(), 0, codeBytes.length);
@ -557,6 +623,34 @@ public class MachineState {
System.arraycopy(stateBytes, byteBuffer.position(), state.userStackByteBuffer.array(), state.userStackByteBuffer.position(), userStackLength); System.arraycopy(stateBytes, byteBuffer.position(), state.userStackByteBuffer.array(), state.userStackByteBuffer.position(), userStackLength);
byteBuffer.position(byteBuffer.position() + userStackLength); byteBuffer.position(byteBuffer.position() + userStackLength);
extractMisc(byteBuffer, state);
return state;
}
/** For restoring only flags from a previously serialized machine state */
public static MachineState flagsOnlyfromBytes(byte[] stateBytes) {
ByteBuffer byteBuffer = ByteBuffer.wrap(stateBytes);
MachineState state = new MachineState(byteBuffer);
// Skip data segment
byteBuffer.position(byteBuffer.position() + state.numDataPages * state.constants.DATA_PAGE_SIZE);
// Skip call stack
int callStackLength = byteBuffer.getInt();
byteBuffer.position(byteBuffer.position() + callStackLength);
// Skip user stack
int userStackLength = byteBuffer.getInt();
byteBuffer.position(byteBuffer.position() + userStackLength);
extractMisc(byteBuffer, state);
return state;
}
private static void extractMisc(ByteBuffer byteBuffer, MachineState state) {
// Actual state // Actual state
state.programCounter = byteBuffer.getInt(); state.programCounter = byteBuffer.getInt();
state.onStopAddress = byteBuffer.getInt(); state.onStopAddress = byteBuffer.getInt();
@ -599,24 +693,23 @@ public class MachineState {
state.b3 = byteBuffer.getLong(); state.b3 = byteBuffer.getLong();
state.b4 = byteBuffer.getLong(); state.b4 = byteBuffer.getLong();
} }
return state;
} }
/** Returns data bytes from saved state to allow external analysis, e.g. confirming expected payouts, etc. */ /** Returns data bytes from saved state to allow external analysis, e.g. confirming expected payouts, etc. */
public static byte[] extractDataBytes(AtLoggerFactory loggerFactory, byte[] stateBytes) { public static byte[] extractDataBytes(byte[] stateBytes) {
ByteBuffer byteBuffer = ByteBuffer.wrap(stateBytes); ByteBuffer byteBuffer = ByteBuffer.wrap(stateBytes);
byte[] headerBytes = new byte[HEADER_LENGTH]; short version = byteBuffer.getShort(0);
byteBuffer.get(headerBytes); VersionedConstants constants = VERSIONED_CONSTANTS.get(version);
MachineState state = new MachineState(headerBytes, null, loggerFactory); short numDataPages = byteBuffer.getShort(2 /*version*/ + 2 /*reserved*/ + 2 /*code pages*/);
// Extract data bytes // Extract data bytes
int dataBytesLength = state.dataByteBuffer.capacity(); int dataBytesLength = numDataPages * constants.DATA_PAGE_SIZE;
byte[] dataBytes = new byte[dataBytesLength]; byte[] dataBytes = new byte[dataBytesLength];
// More efficient than ByteBuffer.get() // More efficient than ByteBuffer.get()
System.arraycopy(stateBytes, byteBuffer.position(), dataBytes, 0, dataBytesLength); System.arraycopy(stateBytes, HEADER_LENGTH, dataBytes, 0, dataBytesLength);
return dataBytes; return dataBytes;
} }
@ -649,6 +742,11 @@ public class MachineState {
} }
} }
/** Convert short to big-endian byte array */
public static byte[] toByteArray(short value) {
return new byte[] { (byte) (value >> 8), (byte) (value) };
}
/** Convert int to big-endian byte array */ /** Convert int to big-endian byte array */
public static byte[] toByteArray(int value) { public static byte[] toByteArray(int value) {
return new byte[] { (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) (value) }; return new byte[] { (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) (value) };

View File

@ -107,6 +107,8 @@ public class SerializationTests extends ExecutableTest {
byte[] packedRestoredSate = restoredState.toBytes(); byte[] packedRestoredSate = restoredState.toBytes();
assertTrue(Arrays.equals(packedState, packedRestoredSate)); assertTrue(Arrays.equals(packedState, packedRestoredSate));
restoredState = MachineState.fromBytes(api, loggerFactory, packedState, codeBytes);
} }
private byte[] simulate() { private byte[] simulate() {