3
0
mirror of https://github.com/Qortal/AT.git synced 2025-01-30 10:52: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>
<groupId>org.ciyam</groupId>
<artifactId>AT</artifactId>
<version>1.3.6</version>
<version>1.3.7</version>
<packaging>jar</packaging>
<properties>
<skipTests>true</skipTests>

View File

@ -3,7 +3,6 @@ package org.ciyam.at;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@ -65,8 +64,6 @@ public class MachineState {
public final short numUserStackPages;
public final long minActivationAmount;
private final byte[] headerBytes;
/** Constants set in effect */
private final VersionedConstants constants;
@ -137,15 +134,10 @@ public class MachineState {
// Constructors
/** For internal use when recreating a machine state */
private MachineState(byte[] headerBytes, API api, AtLoggerFactory loggerFactory) {
if (headerBytes.length != HEADER_LENGTH)
throw new IllegalArgumentException("headerBytes length " + headerBytes.length + " incorrect, expected " + HEADER_LENGTH);
this.headerBytes = headerBytes;
// Parsing header bytes
ByteBuffer byteBuffer = ByteBuffer.wrap(this.headerBytes);
/** For internal use when recreating a machine state. Leaves ByteBuffer position immediately after header. */
private MachineState(ByteBuffer byteBuffer) {
if (byteBuffer.remaining() < HEADER_LENGTH)
throw new IllegalArgumentException("ByteBuffer too small (" + byteBuffer.remaining() + "), minimum " + HEADER_LENGTH);
this.version = byteBuffer.getShort();
if (this.version < 1)
@ -174,8 +166,13 @@ public class MachineState {
throw new IllegalArgumentException("Number of user stack pages must be >= 0");
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.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.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 */
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;
if (creationBytes.length != expectedLength)
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.numCodePages * this.constants.CODE_PAGE_SIZE, this.dataByteBuffer.array(), 0,
this.numDataPages * this.constants.DATA_PAGE_SIZE);
commonFinalConstruction();
commonFinalConstruction(api, loggerFactory);
}
/** For creating a new machine state - used in tests */
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)
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)
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(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.onStopAddress = 0;
this.onErrorAddress = null;
@ -443,33 +445,89 @@ public class MachineState {
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 */
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 {
// 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
bytes.write(this.dataByteBuffer.array());
// Call stack length (32bit unsigned int)
int callStackLength = this.callStackByteBuffer.limit() - this.callStackByteBuffer.position();
bytes.write(toByteArray(callStackLength));
bytes.writeInt(callStackLength);
// Call stack (only the bytes actually in use)
bytes.write(this.callStackByteBuffer.array(), this.callStackByteBuffer.position(), callStackLength);
// User stack length (32bit unsigned int)
int userStackLength = this.userStackByteBuffer.limit() - this.userStackByteBuffer.position();
bytes.write(toByteArray(userStackLength));
bytes.writeInt(userStackLength);
// User stack (only the bytes actually in use)
bytes.write(this.userStackByteBuffer.array(), this.userStackByteBuffer.position(), userStackLength);
// Actual state
bytes.write(toByteArray(this.programCounter));
bytes.write(toByteArray(this.onStopAddress));
bytes.write(toByteArray(this.previousBalance));
bytes.writeInt(this.programCounter);
bytes.writeInt(this.onStopAddress);
bytes.writeLong(this.previousBalance);
// Various 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;
flags.push(hasNonZeroB);
bytes.write(toByteArray(flags.intValue()));
bytes.writeInt(flags.intValue());
// Optional flag-indicated extra info in same order as above
if (this.onErrorAddress != null)
bytes.write(toByteArray(this.onErrorAddress));
bytes.writeInt(this.onErrorAddress);
if (this.sleepUntilHeight != null)
bytes.write(toByteArray(this.sleepUntilHeight));
bytes.writeInt(this.sleepUntilHeight);
if (this.frozenBalance != null)
bytes.write(toByteArray(this.frozenBalance));
bytes.writeLong(this.frozenBalance);
if (hasNonZeroA) {
bytes.write(toByteArray(this.a1));
bytes.write(toByteArray(this.a2));
bytes.write(toByteArray(this.a3));
bytes.write(toByteArray(this.a4));
bytes.writeLong(this.a1);
bytes.writeLong(this.a2);
bytes.writeLong(this.a3);
bytes.writeLong(this.a4);
}
if (hasNonZeroB) {
bytes.write(toByteArray(this.b1));
bytes.write(toByteArray(this.b2));
bytes.write(toByteArray(this.b3));
bytes.write(toByteArray(this.b4));
bytes.writeLong(this.b1);
bytes.writeLong(this.b2);
bytes.writeLong(this.b3);
bytes.writeLong(this.b4);
}
} catch (IOException e) {
return null;
@ -525,14 +583,22 @@ public class MachineState {
public static MachineState fromBytes(API api, AtLoggerFactory loggerFactory, byte[] stateBytes, byte[] codeBytes) {
ByteBuffer byteBuffer = ByteBuffer.wrap(stateBytes);
byte[] headerBytes = new byte[HEADER_LENGTH];
byteBuffer.get(headerBytes);
MachineState state = new MachineState(byteBuffer);
MachineState state = new MachineState(headerBytes, api, loggerFactory);
if (codeBytes.length != state.codeByteBuffer.capacity())
if (codeBytes.length != state.numCodePages * state.constants.CODE_PAGE_SIZE)
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
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);
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
state.programCounter = byteBuffer.getInt();
state.onStopAddress = byteBuffer.getInt();
@ -599,24 +693,23 @@ public class MachineState {
state.b3 = byteBuffer.getLong();
state.b4 = byteBuffer.getLong();
}
return state;
}
/** 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);
byte[] headerBytes = new byte[HEADER_LENGTH];
byteBuffer.get(headerBytes);
short version = byteBuffer.getShort(0);
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
int dataBytesLength = state.dataByteBuffer.capacity();
int dataBytesLength = numDataPages * constants.DATA_PAGE_SIZE;
byte[] dataBytes = new byte[dataBytesLength];
// More efficient than ByteBuffer.get()
System.arraycopy(stateBytes, byteBuffer.position(), dataBytes, 0, dataBytesLength);
System.arraycopy(stateBytes, HEADER_LENGTH, dataBytes, 0, dataBytesLength);
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 */
public static byte[] toByteArray(int 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();
assertTrue(Arrays.equals(packedState, packedRestoredSate));
restoredState = MachineState.fromBytes(api, loggerFactory, packedState, codeBytes);
}
private byte[] simulate() {