Skip to content

Commit

Permalink
core/vm: speed up push and interpreter loop (ethereum#30662)
Browse files Browse the repository at this point in the history
Looking at the cpu profile of a burntpix benchmark, I noticed that a lot
of time was spent in gas-used, in the interpreter loop. It's an actual
call (not inlined), which explicitly wants to be ignored by tracing
("tracing.GasChangeIgnored"), so it can be safely and simply inlined.

The other change is in `pushX`. These also do a call to
`common.RightPadBytes`. I replaced that by a doing a corresponding `Lsh`
on the `u256` if needed. Note: it's needed only to make the stack output
look right, for fuzzers. It technically doesn't matter what we put
there: if code ends on a pushdata immediate, nothing will consume the
stack element. We could just as well just ignore it, if we didn't care
about fuzzers (which I do).

Seems quite a lot faster on burntpix, according to my runs. 

This PR:
```
EVM gas used:    5642735088
execution time:  34.84609475s
allocations:     915683
allocated bytes: 175334088
```
```
EVM gas used:    5642735088
execution time:  36.671958278s
allocations:     915701
allocated bytes: 175340528
```

Master
```
EVM gas used:    5642735088
execution time:  49.349209526s
allocations:     915684
allocated bytes: 175333368
```
```
EVM gas used:    5642735088
execution time:  46.581006598s
allocations:     915681
allocated bytes: 175330728
```

---------

Co-authored-by: Sina M <[email protected]>
Co-authored-by: Felix Lange <[email protected]>
  • Loading branch information
3 people authored Oct 30, 2024
1 parent 87465e9 commit 25bc077
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 8 deletions.
12 changes: 6 additions & 6 deletions core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -984,13 +984,13 @@ func makePush(size uint64, pushByteSize int) executionFunc {
start = min(codeLen, int(*pc+1))
end = min(codeLen, start+pushByteSize)
)
scope.Stack.push(new(uint256.Int).SetBytes(
common.RightPadBytes(
scope.Contract.Code[start:end],
pushByteSize,
)),
)
a := new(uint256.Int).SetBytes(scope.Contract.Code[start:end])

// Missing bytes: pushByteSize - len(pushData)
if missing := pushByteSize - (end - start); missing > 0 {
a.Lsh(a, uint(8*missing))
}
scope.Stack.push(a)
*pc += size
return nil, nil
}
Expand Down
72 changes: 72 additions & 0 deletions core/vm/instructions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -927,3 +927,75 @@ func TestOpMCopy(t *testing.T) {
}
}
}

// TestPush sanity-checks how code with immediates are handled when the code size is
// smaller than the size of the immediate.
func TestPush(t *testing.T) {
code := common.FromHex("0011223344556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121")

push32 := makePush(32, 32)

scope := &ScopeContext{
Memory: nil,
Stack: newstack(),
Contract: &Contract{
Code: code,
},
}
for i, want := range []string{
"0x11223344556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1",
"0x223344556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1",
"0x3344556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1",
"0x44556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1",
"0x556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1",
"0x6677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a1",
"0x77889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a191",
"0x889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181",
"0x9900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a1918171",
"0xaabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a191817161",
"0xaabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151",
"0xbbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a1918171615141",
"0xccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a191817161514131",
"0xddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121",
"0xeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a1918171615141312100",
"0xff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a191817161514131210000",
"0x102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121000000",
"0x2030405060708090a0b0c0d0e0ff1e1d1c1b1a1918171615141312100000000",
"0x30405060708090a0b0c0d0e0ff1e1d1c1b1a191817161514131210000000000",
"0x405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121000000000000",
"0x5060708090a0b0c0d0e0ff1e1d1c1b1a1918171615141312100000000000000",
"0x60708090a0b0c0d0e0ff1e1d1c1b1a191817161514131210000000000000000",
"0x708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121000000000000000000",
"0x8090a0b0c0d0e0ff1e1d1c1b1a1918171615141312100000000000000000000",
"0x90a0b0c0d0e0ff1e1d1c1b1a191817161514131210000000000000000000000",
"0xa0b0c0d0e0ff1e1d1c1b1a19181716151413121000000000000000000000000",
"0xb0c0d0e0ff1e1d1c1b1a1918171615141312100000000000000000000000000",
"0xc0d0e0ff1e1d1c1b1a191817161514131210000000000000000000000000000",
"0xd0e0ff1e1d1c1b1a19181716151413121000000000000000000000000000000",
"0xe0ff1e1d1c1b1a1918171615141312100000000000000000000000000000000",
"0xff1e1d1c1b1a191817161514131210000000000000000000000000000000000",
"0xf1e1d1c1b1a19181716151413121000000000000000000000000000000000000",
"0xe1d1c1b1a1918171615141312100000000000000000000000000000000000000",
"0xd1c1b1a191817161514131210000000000000000000000000000000000000000",
"0xc1b1a19181716151413121000000000000000000000000000000000000000000",
"0xb1a1918171615141312100000000000000000000000000000000000000000000",
"0xa191817161514131210000000000000000000000000000000000000000000000",
"0x9181716151413121000000000000000000000000000000000000000000000000",
"0x8171615141312100000000000000000000000000000000000000000000000000",
"0x7161514131210000000000000000000000000000000000000000000000000000",
"0x6151413121000000000000000000000000000000000000000000000000000000",
"0x5141312100000000000000000000000000000000000000000000000000000000",
"0x4131210000000000000000000000000000000000000000000000000000000000",
"0x3121000000000000000000000000000000000000000000000000000000000000",
"0x2100000000000000000000000000000000000000000000000000000000000000",
"0x0",
} {
pc := new(uint64)
*pc = uint64(i)
push32(pc, nil, scope)
res := scope.Stack.pop()
if have := res.Hex(); have != want {
t.Fatalf("case %d, have %v want %v", i, have, want)
}
}
}
10 changes: 8 additions & 2 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
} else if sLen > operation.maxStack {
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
}
if !contract.UseGas(cost, in.evm.Config.Tracer, tracing.GasChangeIgnored) {
// for tracing: this gas consumption event is emitted below in the debug section.
if contract.Gas < cost {
return nil, ErrOutOfGas
} else {
contract.Gas -= cost
}

if operation.dynamicGas != nil {
Expand Down Expand Up @@ -279,8 +282,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
}
if !contract.UseGas(dynamicCost, in.evm.Config.Tracer, tracing.GasChangeIgnored) {
// for tracing: this gas consumption event is emitted below in the debug section.
if contract.Gas < dynamicCost {
return nil, ErrOutOfGas
} else {
contract.Gas -= dynamicCost
}

// Do tracing before memory expansion
Expand Down
12 changes: 12 additions & 0 deletions core/vm/runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,17 @@ func BenchmarkSimpleLoop(b *testing.B) {
byte(vm.JUMP),
}

loopingCode2 := []byte{
byte(vm.JUMPDEST), // [ count ]
// push args for the call
byte(vm.PUSH4), 1, 2, 3, 4,
byte(vm.PUSH5), 1, 2, 3, 4, 5,

byte(vm.POP), byte(vm.POP),
byte(vm.PUSH6), 0, 0, 0, 0, 0, 0, // jumpdestination
byte(vm.JUMP),
}

callRevertingContractWithInput := []byte{
byte(vm.JUMPDEST), //
// push args for the call
Expand All @@ -540,6 +551,7 @@ func BenchmarkSimpleLoop(b *testing.B) {
benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", "", b)
benchmarkNonModifyingCode(100000000, callIdentity, "call-identity-100M", "", b)
benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", "", b)
benchmarkNonModifyingCode(100000000, loopingCode2, "loop2-100M", "", b)
benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", "", b)
benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", "", b)
benchmarkNonModifyingCode(100000000, callRevertingContractWithInput, "call-reverting-100M", "", b)
Expand Down

0 comments on commit 25bc077

Please sign in to comment.