core/vm: speed up push and interpreter loop (#30662)

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 <1591639+s1na@users.noreply.github.com>
Co-authored-by: Felix Lange <fjl@twurst.com>
pull/30710/head
Martin HS 4 weeks ago committed by GitHub
parent 87465e98f9
commit 25bc07749c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 12
      core/vm/instructions.go
  2. 72
      core/vm/instructions_test.go
  3. 10
      core/vm/interpreter.go
  4. 12
      core/vm/runtime/runtime_test.go

@ -984,13 +984,13 @@ func makePush(size uint64, pushByteSize int) executionFunc {
start = min(codeLen, int(*pc+1)) start = min(codeLen, int(*pc+1))
end = min(codeLen, start+pushByteSize) end = min(codeLen, start+pushByteSize)
) )
scope.Stack.push(new(uint256.Int).SetBytes( a := new(uint256.Int).SetBytes(scope.Contract.Code[start:end])
common.RightPadBytes(
scope.Contract.Code[start:end],
pushByteSize,
)),
)
// Missing bytes: pushByteSize - len(pushData)
if missing := pushByteSize - (end - start); missing > 0 {
a.Lsh(a, uint(8*missing))
}
scope.Stack.push(a)
*pc += size *pc += size
return nil, nil return nil, nil
} }

@ -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)
}
}
}

@ -249,8 +249,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
} else if sLen > operation.maxStack { } else if sLen > operation.maxStack {
return nil, &ErrStackOverflow{stackLen: sLen, limit: 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 return nil, ErrOutOfGas
} else {
contract.Gas -= cost
} }
if operation.dynamicGas != nil { if operation.dynamicGas != nil {
@ -279,8 +282,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) 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 return nil, ErrOutOfGas
} else {
contract.Gas -= dynamicCost
} }
// Do tracing before memory expansion // Do tracing before memory expansion

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

Loading…
Cancel
Save