Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Fast Sync

### Additions and Improvements
- Performance improvements to EQ opcode [#9229](https://github.com/hyperledger/besu/pull/9229)

### Bug fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
import org.hyperledger.besu.evm.worldstate.WorldUpdater;

import java.util.Random;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.function.Supplier;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
Expand Down Expand Up @@ -82,15 +85,85 @@ public static MessageFrame createMessageCallFrameWithCallData(final Bytes callDa
*/
public static void fillPool(final Bytes[] pool) {
final Random random = new Random();
fillPool(pool, () -> 1 + random.nextInt(32)); // [1, 32]
}

/**
* Fills an array with random byte values of the specified size.
*
* @param pool the destination array
* @param sizeSupplier size of the Bytes
*/
public static void fillPool(final Bytes[] pool, final Supplier<Integer> sizeSupplier) {
final Random random = new Random();
for (int i = 0; i < pool.length; i++) {
final int aSize = 1 + random.nextInt(32); // [1, 32]
final byte[] a = new byte[aSize];
final byte[] a = new byte[sizeSupplier.get()];
random.nextBytes(a);
pool[i] = Bytes.wrap(a);
}
}

/**
* FIlls an array with random byte values of the specified size that fulfil the specified
* condition.
*
* @param pool the destination array
* @param sizeSupplier size of the Bytes
* @param condition predicate for whether to add value to the pool
*/
public static void fillPool(
final Bytes[] pool, final Supplier<Integer> sizeSupplier, final Predicate<Bytes> condition) {
final Random random = new Random();
for (int i = 0; i < pool.length; i++) {
final byte[] arrayA = new byte[sizeSupplier.get()];
Bytes a;
do {
random.nextBytes(arrayA);
a = Bytes.wrap(arrayA);
} while (!condition.test(a));
pool[i] = a;
}
}

/**
* Fill 2 arrays with random byte values that have a relationship between elements of a specified
* size.
*
* @param aPool destination array for pool a
* @param bPool destination array for pool b
* @param aSizeSupplier size of the Bytes for pool a
* @param bSizeSupplier size of the Bytes for pool b
* @param condition whether to include generated bytes in pools
*/
public static void fillPools(
final Bytes[] aPool,
final Bytes[] bPool,
final Supplier<Integer> aSizeSupplier,
final Supplier<Integer> bSizeSupplier,
final BiPredicate<Bytes, Bytes> condition) {

if (aPool.length != bPool.length) {
throw new IllegalArgumentException("pools should have same length");
}

final Random random = new Random();
for (int i = 0; i < aPool.length; i++) {
final int aSize = aSizeSupplier.get();
final int bSize = bSizeSupplier.get();
Bytes a, b;
do {
final byte[] arrayA = new byte[aSize];
final byte[] arrayB = new byte[bSize];
random.nextBytes(arrayA);
random.nextBytes(arrayB);
a = Bytes.wrap(arrayA);
b = Bytes.wrap(arrayB);
} while (!condition.test(a, b));
aPool[i] = a;
bPool[i] = b;
}
}

static Bytes createCallData(final int size, final boolean nonZero) {
byte[] data = new byte[size];
if (nonZero) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,158 @@
import org.hyperledger.besu.evm.operation.EqOperation;
import org.hyperledger.besu.evm.operation.Operation;

import java.util.Arrays;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.bytes.MutableBytes;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;

public class EqOperationBenchmark extends BinaryOperationBenchmark {

// public because of jmh code generator
public enum MODE {
EMPTY_INPUTS,
ONE_EMPTY_INPUT,
BYTES1_EQUAL,
BYTES1_NOT_EQUAL,
BYTES1_EQUAL_WITH_ZEROS,
BYTES1_NOT_EQUAL_WITH_ZEROS,
BYTES16_EQUAL,
BYTES16_NOT_EQUAL,
BYTES16_EQUAL_WITH_ZEROS,
BYTES16_NOT_EQUAL_WITH_ZEROS,
BYTES32_NOT_EQUAL_MOST_SIGNIFICANT,
BYTES32_NOT_EQUAL_LEAST_SIGNIFICANT,
BYTES32_RANDOM,
BYTES30_BYTES16_RANDOM,
BYTES32_EQUAL_BYTE_REPEAT
}

@Param private MODE mode;

@Setup
@Override
public void setUp() {
frame = BenchmarkHelper.createMessageCallFrame();

aPool = new Bytes[SAMPLE_SIZE];
bPool = new Bytes[SAMPLE_SIZE];
switch (mode) {
case EMPTY_INPUTS:
Arrays.fill(aPool, Bytes.EMPTY);
Arrays.fill(bPool, Bytes.EMPTY);
break;
case ONE_EMPTY_INPUT:
Arrays.fill(aPool, Bytes.EMPTY);
BenchmarkHelper.fillPool(bPool, () -> 16);
bPool = Arrays.stream(bPool).map(Bytes32::leftPad).toArray(Bytes[]::new);
break;
case BYTES1_EQUAL:
BenchmarkHelper.fillPool(aPool, () -> 1);
bPool =
Arrays.stream(aPool)
.map(bytes -> Bytes.wrap(bytes.toArrayUnsafe()))
.toArray(Bytes[]::new);
break;
case BYTES1_NOT_EQUAL:
BenchmarkHelper.fillPools(
aPool, bPool, () -> 1, () -> 1, (value1, value2) -> !value1.equals(value2));
break;
case BYTES1_EQUAL_WITH_ZEROS:
BenchmarkHelper.fillPool(aPool, () -> 1);
aPool = Arrays.stream(aPool).map(Bytes32::leftPad).toArray(Bytes[]::new);
bPool =
Arrays.stream(aPool)
.map(bytes -> Bytes.wrap(bytes.toArrayUnsafe()))
.toArray(Bytes[]::new);
break;
case BYTES1_NOT_EQUAL_WITH_ZEROS:
BenchmarkHelper.fillPools(
aPool, bPool, () -> 1, () -> 1, (value1, value2) -> !value1.equals(value2));
aPool = Arrays.stream(aPool).map(Bytes32::leftPad).toArray(Bytes[]::new);
bPool = Arrays.stream(bPool).map(Bytes32::leftPad).toArray(Bytes[]::new);
break;
case BYTES16_EQUAL:
BenchmarkHelper.fillPool(aPool, () -> 16);
bPool =
Arrays.stream(aPool)
.map(bytes -> Bytes.wrap(bytes.toArrayUnsafe()))
.toArray(Bytes[]::new);
break;
case BYTES16_NOT_EQUAL:
BenchmarkHelper.fillPools(
aPool, bPool, () -> 16, () -> 16, (value1, value2) -> !value1.equals(value2));
break;
case BYTES16_EQUAL_WITH_ZEROS:
BenchmarkHelper.fillPool(aPool, () -> 16);
aPool = Arrays.stream(aPool).map(Bytes32::leftPad).toArray(Bytes[]::new);
bPool =
Arrays.stream(aPool)
.map(bytes -> Bytes.wrap(bytes.toArrayUnsafe()))
.toArray(Bytes[]::new);
break;
case BYTES16_NOT_EQUAL_WITH_ZEROS:
BenchmarkHelper.fillPools(
aPool, bPool, () -> 16, () -> 16, (value1, value2) -> !value1.equals(value2));
aPool = Arrays.stream(aPool).map(Bytes32::leftPad).toArray(Bytes[]::new);
bPool = Arrays.stream(bPool).map(Bytes32::leftPad).toArray(Bytes[]::new);
break;
case BYTES32_NOT_EQUAL_MOST_SIGNIFICANT:
BenchmarkHelper.fillPool(aPool, () -> 32, value -> value.get(0) != 0);
bPool =
Arrays.stream(aPool)
.map(
bytes -> {
final MutableBytes mutableBytes = bytes.mutableCopy();
byte firstByte = bytes.get(0);
mutableBytes.set(0, (byte) (firstByte > 0 ? firstByte + 1 : firstByte - 1));
return mutableBytes;
})
.toArray(Bytes[]::new);
break;
case BYTES32_NOT_EQUAL_LEAST_SIGNIFICANT:
BenchmarkHelper.fillPool(aPool, () -> 32, value -> value.get(0) != 0);
bPool =
Arrays.stream(aPool)
.map(
bytes -> {
final MutableBytes mutableBytes = bytes.mutableCopy();
byte lastByte = bytes.get(bytes.size() - 1);
mutableBytes.set(
bytes.size() - 1, (byte) (lastByte > 0 ? lastByte + 1 : lastByte - 1));
return mutableBytes;
})
.toArray(Bytes[]::new);
break;
case BYTES32_RANDOM:
BenchmarkHelper.fillPool(aPool, () -> 32, value -> value.get(0) != 0);
BenchmarkHelper.fillPool(bPool, () -> 32, value -> value.get(0) != 0);
break;
case BYTES30_BYTES16_RANDOM:
BenchmarkHelper.fillPool(aPool, () -> 30, value -> value.get(0) != 0);
BenchmarkHelper.fillPool(bPool, () -> 16, value -> value.get(0) != 0);
break;
case BYTES32_EQUAL_BYTE_REPEAT:
BenchmarkHelper.fillPool(aPool, () -> 1);
aPool =
Arrays.stream(aPool)
.map(
bytes -> {
final byte[] newPool = new byte[32];
Arrays.fill(newPool, bytes.get(0));
return Bytes.wrap(newPool);
})
.toArray(Bytes[]::new);
bPool =
Arrays.stream(aPool)
.map(bytes -> Bytes.wrap(bytes.toArrayUnsafe()))
.toArray(Bytes[]::new);
}
index = 0;
}

@Override
protected Operation.OperationResult invoke(final MessageFrame frame) {
return EqOperation.staticOperation(frame);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;

import java.util.Arrays;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;

/** The Eq operation. */
public class EqOperation extends AbstractFixedCostOperation {

private static final byte[] ZEROS = new byte[32];

/** The Eq operation success result. */
static final OperationResult eqSuccess = new OperationResult(3, null);

Expand All @@ -49,13 +53,21 @@ public Operation.OperationResult executeFixedCostOperation(
* @return the operation result
*/
public static OperationResult staticOperation(final MessageFrame frame) {
final Bytes value0 = frame.popStackItem().trimLeadingZeros();
final Bytes value1 = frame.popStackItem().trimLeadingZeros();

final Bytes result = (value0.equals(value1) ? UInt256.ONE : UInt256.ZERO);
final byte[] a = frame.popStackItem().toArrayUnsafe();
final byte[] b = frame.popStackItem().toArrayUnsafe();
final int nonZeroA = firstNonZeroIndex(a);
final int nonZeroB = firstNonZeroIndex(b);
Bytes result = UInt256.ZERO;
if (Arrays.equals(a, nonZeroA, a.length, b, nonZeroB, b.length)) {
result = UInt256.ONE;
}

frame.pushStackItem(result);

return eqSuccess;
}

private static int firstNonZeroIndex(final byte[] value) {
final int m = Arrays.mismatch(value, 0, value.length, ZEROS, 0, value.length);
return m == -1 ? value.length : m;
}
}
Loading