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>
This commit is contained in:
parent
87465e98f9
commit
25bc07749c
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
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 {
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue