Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce UInt256 in EVMOperations #5331

Merged
merged 17 commits into from
Apr 20, 2023

Conversation

shemnon
Copy link
Contributor

@shemnon shemnon commented Apr 11, 2023

PR description

Improve the performance of the EVM by reducing the usage of UInt256 in almost all operations and moving the main switch to a Java 17 expression switch

Fixed Issue(s)

Reduce the use of UInt256 in operations, replacing them with Bytes as
appropriate.

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
@github-actions
Copy link

github-actions bot commented Apr 11, 2023

  • I thought about documentation and added the doc-change-required label to this PR if updates are required.
  • I have considered running ./gradlew acceptanceTestNonMainnet locally if my PR affects non-mainnet modules.
  • I thought about the changelog and included a changelog update if required.

…6-in-operations

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
@shemnon
Copy link
Contributor Author

shemnon commented Apr 11, 2023

Benchmark results - https://docs.google.com/spreadsheets/d/1qbvlAveoLCiWbhbxouHgcpFeDVjws7UD0dgZghJZ6oU/edit?usp=sharing

Macbook Pro M1 MAX

Top line result - 10-15% "all in"
Target opcodes - 100-500% improvement. Most notably in JumpDest& SDIV

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
Copy link
Contributor

@macfarla macfarla left a comment

Choose a reason for hiding this comment

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

a few comments on parts I don't understand
overall I think it's fine
performance improvement sounds fab
think a second opinion would be good :)

.thenReturn(UInt256.fromBytes(Bytes32.fromHexStringLenient(shift)))
.thenReturn(UInt256.fromHexString(number));
.thenReturn(Bytes32.fromHexStringLenient(shift))
.thenReturn(Bytes.fromHexString(number));
Copy link
Contributor

Choose a reason for hiding this comment

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

why not Bytes32 everywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's something worth benchmarking, but the number of times we need to take a 256 bit number and consider it as a 8 or 32 bit number are very high (memory access, shifts, etc). So my first pass was trimmed bytes, where we can assess size much quicker.

I'll look into Bytes32 or possibly ByteBuffer integrations in a future pass. It did cross my mind that consistently sized words may improve garbage collection implications and (by extension) speed.

private static Bytes getByte(final Bytes seq, final Bytes offset) {
Bytes trimmedOffset = offset.trimLeadingZeros();
if (trimmedOffset.size() > 1) {
return Bytes.EMPTY;
Copy link
Contributor

Choose a reason for hiding this comment

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

in the cases where return UInt256.ZERO has changed to Bytes.EMPTY - I'm not an EVM expert - there is already test coverage for this? no side effects?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Semantically they are the same on the stack, it's quicker to get an int or byte zero out of a Bytes.EMPTY than a UInt256.ZERO (UInt256 would have to check 7 ints as zero before returning the last one, and possibly bit checking for size).

The tests that detected the Tuweni 3.2 constant values bug (where Bytes32.ZERO couldn't be trimmed to 20 bytes in length) would trip this if it was a problem.

@macfarla
Copy link
Contributor

@shemnon should this get a changelog entry?

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
…6-in-operations

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
Copy link
Contributor

@garyschulte garyschulte left a comment

Choose a reason for hiding this comment

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

the slicing is a bit scary tbh, but happy to see evm perf improvements. This PR would be a good candidate for fuzzing on a private testnet. What kind of testing did you do already?

@@ -184,7 +183,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) {
frame.expandMemory(outputDataOffset(frame), outputDataLength(frame));
frame.incrementRemainingGas(gasAvailableForChildCall(frame) + cost);
frame.popStackItems(getStackItemsConsumed());
frame.pushStackItem(UInt256.ZERO);
frame.pushStackItem(Bytes.EMPTY);
Copy link
Contributor

Choose a reason for hiding this comment

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

FAILURE_STACK_ITEM here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -41,10 +43,10 @@ public BlockHashOperation(final GasCalculator gasCalculator) {
@Override
public Operation.OperationResult executeFixedCostOperation(
final MessageFrame frame, final EVM evm) {
final UInt256 blockArg = UInt256.fromBytes(frame.popStackItem());
final Bytes blockArg = frame.popStackItem().trimLeadingZeros();
Copy link
Contributor

Choose a reason for hiding this comment

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

might this lead to problems if we are not aligned on a word boundary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It won't. The call to blockArg.toLong() on line 54 handles any byte length of 8 or less. That's the only use of this variable.

@@ -21,6 +21,9 @@
/** The Jump dest operation. */
public class JumpDestOperation extends AbstractFixedCostOperation {

/** constant for a successful jumpdest * */
public static final OperationResult jumpdestSuccess = new OperationResult(1L, null);
Copy link
Contributor

Choose a reason for hiding this comment

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

suggest all caps on public static final

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


final UInt256 result = value0.subtract(value1);
final BigInteger result = value0.subtract(value1);
Copy link
Contributor

Choose a reason for hiding this comment

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

surprising that BigInteger is more performant than UInt256

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I expect there are some JVM optimizations targeting that stretch of code. It doesn't look like it's directly an @intrinsic function but it ought to be. Also, the zero check in UInt256 is kind of expensive.

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
@shemnon
Copy link
Contributor Author

shemnon commented Apr 14, 2023

Before I press Merge I was going to give it a day or so on Marius's fuzzer. It found the other corner cases fairly quickly.

@non-fungible-nelson
Copy link
Contributor

How did it go? @shemnon

Use a primitive to bytes function that is more direct than the Bytes
methods.

Signed-off-by: Danno Ferrin <danno.ferrin@gmail.com>
Signed-off-by: Danno Ferrin <danno.ferrin@gmail.com>
@shemnon
Copy link
Contributor Author

shemnon commented Apr 19, 2023

Fuzz testing was useful.

@shemnon shemnon enabled auto-merge (squash) April 20, 2023 03:22
Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
@shemnon shemnon force-pushed the reduce-uint256-in-operations branch from b8ab092 to 1b343e1 Compare April 20, 2023 04:50
@shemnon shemnon merged commit b3d3f54 into hyperledger:main Apr 20, 2023
elenduuche pushed a commit to elenduuche/besu that referenced this pull request Aug 16, 2023
Performance improvements
- Reduce the use of UInt256 in operations, replacing them with Bytes as
   appropriate.
- Use Java17 expression switch

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
eum602 pushed a commit to lacchain/besu that referenced this pull request Nov 3, 2023
Performance improvements
- Reduce the use of UInt256 in operations, replacing them with Bytes as
   appropriate.
- Use Java17 expression switch

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants