Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ab9e3cd
Improve SAR opcode - first round
ahamlat Jan 28, 2026
c6e21bd
Improve SAR opcode - first round
ahamlat Jan 28, 2026
9691670
Improve isShiftOverflow
ahamlat Jan 28, 2026
9b20601
Small refactoring
ahamlat Feb 4, 2026
209e64b
Merge branch 'main' into improve-sar-performance
ahamlat Feb 4, 2026
9ada695
Fix an issue with empty shift
ahamlat Feb 5, 2026
d4363f4
Optimize Sar, Shl and Shr performances
ahamlat Feb 6, 2026
c721e15
Add the configuration for block processing
ahamlat Feb 6, 2026
b3cd533
Optimize isShiftOverflow method
ahamlat Feb 6, 2026
aee6907
Fix "Archive contains more than 65535 entries" error with jmh
ahamlat Feb 6, 2026
f030d93
Add SAR property based testing
ahamlat Feb 9, 2026
cf20a2c
Handle some specific cases
ahamlat Feb 12, 2026
705b033
Use Arrays.equals
ahamlat Feb 12, 2026
bf918a8
Add GC/memory allocations profiling
ahamlat Feb 12, 2026
0ca7aa0
Improve SHIFT_255 use case
ahamlat Feb 12, 2026
06587d2
spotless + fix gcProfiler flag issue
ahamlat Feb 12, 2026
3cabb0d
Merge branch 'main' into improve-shift-opcodes
ahamlat Feb 12, 2026
e4b8bbe
Delete a debug class
ahamlat Feb 12, 2026
78126d8
Add randomness to JMH benchmarks
ahamlat Feb 13, 2026
ef66ae7
Merge branch 'main' into improve-shift-opcodes
ahamlat Feb 13, 2026
ca544de
Update evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperat…
ahamlat Feb 13, 2026
268036a
Remove redundant & 0xFF
ahamlat Feb 13, 2026
f95e549
Review variables naming for better readability
ahamlat Feb 16, 2026
a99c10f
Merge branch 'main' into improve-shift-opcodes
ahamlat Feb 16, 2026
93c3623
Merge branch 'main' into improve-shift-opcodes
ahamlat Feb 16, 2026
83c7a17
Add worst case on SAR benchmarks
ahamlat Feb 16, 2026
8c1d5c1
Apply suggestions from code review
ahamlat Feb 17, 2026
a7514b1
Address review comments
ahamlat Feb 17, 2026
a2d7ffd
Fix test issue.
ahamlat Feb 17, 2026
31d0669
spotless.
ahamlat Feb 17, 2026
63eb9f2
Update evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperat…
ahamlat Feb 17, 2026
86a85e6
Merge branch 'main' into improve-shift-opcodes
ahamlat Feb 17, 2026
8dd0384
Merge branch 'main' into improve-shift-opcodes
ahamlat Feb 19, 2026
8b30c59
Renaming
ahamlat Feb 19, 2026
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 @@ -45,6 +45,7 @@

#### Performance
- EVM optimisations - Improves 70% of EEST benchmarks [#9775](https://github.com/hyperledger/besu/pull/9775)
- EVM optimisations - Improve SAR, SHR and SHL opcodes performance [#9796](https://github.com/hyperledger/besu/pull/9796)

### Bug fixes
- Fix QBFT Shanghai support by reintroducing NotApplicableWithdrawals withdrawals validator [#9830](https://github.com/hyperledger/besu/pull/9830)
Expand Down
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ subprojects {
excludes = _strListCmdArg('excludes', [])
var asyncProfiler = _strCmdArg('asyncProfiler')
var asyncProfilerOptions = _strCmdArg('asyncProfilerOptions', 'output=flamegraph')
zip64.set(true)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this on main too so watch out for conflicts/duplication

jvmArgs = [
'-XX:+EnableDynamicAgentLoading'
]
Expand All @@ -666,6 +667,10 @@ subprojects {
// Force debug symbols of stack traces to the profiler
jvmArgs.addAll("-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints")
}
var gcProfiler = _strCmdArg('gcProfiler')
if (gcProfiler != null && gcProfiler.toBoolean()) {
profilers.add('gc')
}
Comment on lines +670 to +673
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice addition 👍

duplicateClassesStrategy = DuplicatesStrategy.INCLUDE
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.vm.operations;

import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomNegativeValue;
import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomPositiveValue;
import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomValue;

import java.util.concurrent.ThreadLocalRandom;

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

/**
* Abstract base class for SAR (Shift Arithmetic Right) operation benchmarks.
*
* <p>SAR has additional test cases for negative/positive values to test sign extension behavior.
*/
public abstract class AbstractSarOperationBenchmark extends BinaryOperationBenchmark {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to shift by a negative amount? If not, what code is guarding that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not possible as it is spec'ed. The first input (shift) of SAR is unsigned.
The line below reads the shift amount as unsigned

    final int shift = shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF);


/** Test cases covering different execution paths for SAR operations. */
public enum Case {
/** Shift by 0 - early return path. */
SHIFT_0,
/** Negative number (ALL_BITS) with shift=1 - tests sign extension OR path. */
NEGATIVE_SHIFT_1,
/** value with all bits to 1 with shift=1 * */
ALL_BITS_SHIFT_1,
/** Positive number with shift=1 - no sign extension needed. */
POSITIVE_SHIFT_1,
/** Negative number with medium shift. */
NEGATIVE_SHIFT_128,
/** Negative number with max shift. */
NEGATIVE_SHIFT_255,
/** Positive number with medium shift. */
POSITIVE_SHIFT_128,
/** positive number with max shift. */
POSITIVE_SHIFT_255,
/** Overflow: shift >= 256. */
OVERFLOW_SHIFT_256,
/** Overflow: shift amount > 4 bytes. */
OVERFLOW_LARGE_SHIFT,
/** Random values (original behavior). */
FULL_RANDOM
}

/** All bits set (32 bytes of 0xFF) - represents -1 in two's complement. */
protected static final Bytes ALL_BITS =
Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");

@Param({
"SHIFT_0",
"NEGATIVE_SHIFT_1",
"POSITIVE_SHIFT_1",
"ALL_BITS_SHIFT_1",
"NEGATIVE_SHIFT_128",
"NEGATIVE_SHIFT_255",
"POSITIVE_SHIFT_128",
"POSITIVE_SHIFT_255",
"OVERFLOW_SHIFT_256",
"OVERFLOW_LARGE_SHIFT",
"FULL_RANDOM"
})
protected String caseName;

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

final Case scenario = Case.valueOf(caseName);
aPool = new Bytes[SAMPLE_SIZE]; // shift amount (pushed second, popped first)
bPool = new Bytes[SAMPLE_SIZE]; // value (pushed first, popped second)

final ThreadLocalRandom random = ThreadLocalRandom.current();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we keep using ThreadLocalRandom ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is more thread safe, it doesn't help a lot in this case but it is in general a best practice.


for (int i = 0; i < SAMPLE_SIZE; i++) {
switch (scenario) {
case SHIFT_0:
aPool[i] = Bytes.of(0);
bPool[i] = randomValue(random);
break;

case NEGATIVE_SHIFT_1:
// shiftAmount = 0x1, value = 0xfff...fff (negative, tests OR path)
aPool[i] = Bytes.of(1);
bPool[i] = randomNegativeValue(random);
break;

case ALL_BITS_SHIFT_1:
// shiftAmount = 0x1, value = 0xfff...fff (negative, tests OR path)
aPool[i] = Bytes.of(1);
bPool[i] = ALL_BITS;
break;

case POSITIVE_SHIFT_1:
// shiftAmount = 0x1, random positive value (no sign extension)
aPool[i] = Bytes.of(1);
bPool[i] = randomPositiveValue(random);
break;

case NEGATIVE_SHIFT_128:
aPool[i] = Bytes.of(128);
bPool[i] = randomNegativeValue(random);
break;

case NEGATIVE_SHIFT_255:
aPool[i] = Bytes.of(255);
bPool[i] = randomNegativeValue(random);
break;

case POSITIVE_SHIFT_128:
aPool[i] = Bytes.of(128);
bPool[i] = randomPositiveValue(random);
break;
case POSITIVE_SHIFT_255:
aPool[i] = Bytes.of(255);
bPool[i] = randomPositiveValue(random);
break;

case OVERFLOW_SHIFT_256:
// Shift of exactly 256 - overflow path
aPool[i] = Bytes.fromHexString("0x0100"); // 256
bPool[i] = randomValue(random);
break;

case OVERFLOW_LARGE_SHIFT:
// Shift amount > 4 bytes - overflow path
aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes
bPool[i] = randomValue(random);
break;

case FULL_RANDOM:
default:
// Original random behavior
final byte[] shift = new byte[1 + random.nextInt(2)];
final byte[] value = new byte[1 + random.nextInt(32)];
random.nextBytes(shift);
random.nextBytes(value);
aPool[i] = Bytes.wrap(shift);
bPool[i] = Bytes.wrap(value);
break;
}
}
index = 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.vm.operations;

import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomValue;

import java.util.concurrent.ThreadLocalRandom;

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

/**
* Abstract base class for shift operation benchmarks (SHL, SHR, SAR).
*
* <p>Provides shared test case definitions and setup logic.
*/
public abstract class AbstractShiftOperationBenchmark extends BinaryOperationBenchmark {

/** Test cases covering different execution paths for shift operations. */
public enum Case {
/** Shift by 0 - no shift needed. */
SHIFT_0,
/** Small shift by 1 bit. */
SHIFT_1,
/** Medium shift by 128 bits (half word). */
SHIFT_128,
/** Large shift by 255 bits (max valid). */
SHIFT_255,
/** Overflow: shift of exactly 256. */
OVERFLOW_SHIFT_256,
/** Overflow: shift amount > 4 bytes. */
OVERFLOW_LARGE_SHIFT,
/** Random values (original behavior). */
FULL_RANDOM
}

@Param({
"SHIFT_0",
"SHIFT_1",
"SHIFT_128",
"SHIFT_255",
"OVERFLOW_SHIFT_256",
"OVERFLOW_LARGE_SHIFT",
"FULL_RANDOM"
})
protected String caseName;

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

final Case scenario = Case.valueOf(caseName);
aPool = new Bytes[SAMPLE_SIZE]; // shift amount (pushed second, popped first)
bPool = new Bytes[SAMPLE_SIZE]; // value (pushed first, popped second)

final ThreadLocalRandom random = ThreadLocalRandom.current();

for (int i = 0; i < SAMPLE_SIZE; i++) {
switch (scenario) {
case SHIFT_0:
aPool[i] = Bytes.of(0);
bPool[i] = randomValue(random);
break;

case SHIFT_1:
aPool[i] = Bytes.of(1);
bPool[i] = randomValue(random);
break;

case SHIFT_128:
aPool[i] = Bytes.of(128);
bPool[i] = randomValue(random);
break;

case SHIFT_255:
aPool[i] = Bytes.of(255);
bPool[i] = randomValue(random);
break;

case OVERFLOW_SHIFT_256:
aPool[i] = Bytes.fromHexString("0x0100"); // 256
bPool[i] = randomValue(random);
break;

case OVERFLOW_LARGE_SHIFT:
aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes
bPool[i] = randomValue(random);
break;

case FULL_RANDOM:
default:
final byte[] shift = new byte[1 + random.nextInt(4)];
final byte[] value = new byte[1 + random.nextInt(32)];
random.nextBytes(shift);
random.nextBytes(value);
aPool[i] = Bytes.wrap(shift);
bPool[i] = Bytes.wrap(value);
break;
}
}
index = 0;
}
}
Loading