Skip to content

Commit 25bc077

Browse files
holimans1nafjl
authored
core/vm: speed up push and interpreter loop (ethereum#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 <[email protected]> Co-authored-by: Felix Lange <[email protected]>
1 parent 87465e9 commit 25bc077

File tree

4 files changed

+98
-8
lines changed

4 files changed

+98
-8
lines changed

core/vm/instructions.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -984,13 +984,13 @@ func makePush(size uint64, pushByteSize int) executionFunc {
984984
start = min(codeLen, int(*pc+1))
985985
end = min(codeLen, start+pushByteSize)
986986
)
987-
scope.Stack.push(new(uint256.Int).SetBytes(
988-
common.RightPadBytes(
989-
scope.Contract.Code[start:end],
990-
pushByteSize,
991-
)),
992-
)
987+
a := new(uint256.Int).SetBytes(scope.Contract.Code[start:end])
993988

989+
// Missing bytes: pushByteSize - len(pushData)
990+
if missing := pushByteSize - (end - start); missing > 0 {
991+
a.Lsh(a, uint(8*missing))
992+
}
993+
scope.Stack.push(a)
994994
*pc += size
995995
return nil, nil
996996
}

core/vm/instructions_test.go

+72
Original file line numberDiff line numberDiff line change
@@ -927,3 +927,75 @@ func TestOpMCopy(t *testing.T) {
927927
}
928928
}
929929
}
930+
931+
// TestPush sanity-checks how code with immediates are handled when the code size is
932+
// smaller than the size of the immediate.
933+
func TestPush(t *testing.T) {
934+
code := common.FromHex("0011223344556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121")
935+
936+
push32 := makePush(32, 32)
937+
938+
scope := &ScopeContext{
939+
Memory: nil,
940+
Stack: newstack(),
941+
Contract: &Contract{
942+
Code: code,
943+
},
944+
}
945+
for i, want := range []string{
946+
"0x11223344556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1",
947+
"0x223344556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1",
948+
"0x3344556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1",
949+
"0x44556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1",
950+
"0x556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1",
951+
"0x6677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a1",
952+
"0x77889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a191",
953+
"0x889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181",
954+
"0x9900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a1918171",
955+
"0xaabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a191817161",
956+
"0xaabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151",
957+
"0xbbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a1918171615141",
958+
"0xccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a191817161514131",
959+
"0xddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121",
960+
"0xeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a1918171615141312100",
961+
"0xff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a191817161514131210000",
962+
"0x102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121000000",
963+
"0x2030405060708090a0b0c0d0e0ff1e1d1c1b1a1918171615141312100000000",
964+
"0x30405060708090a0b0c0d0e0ff1e1d1c1b1a191817161514131210000000000",
965+
"0x405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121000000000000",
966+
"0x5060708090a0b0c0d0e0ff1e1d1c1b1a1918171615141312100000000000000",
967+
"0x60708090a0b0c0d0e0ff1e1d1c1b1a191817161514131210000000000000000",
968+
"0x708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121000000000000000000",
969+
"0x8090a0b0c0d0e0ff1e1d1c1b1a1918171615141312100000000000000000000",
970+
"0x90a0b0c0d0e0ff1e1d1c1b1a191817161514131210000000000000000000000",
971+
"0xa0b0c0d0e0ff1e1d1c1b1a19181716151413121000000000000000000000000",
972+
"0xb0c0d0e0ff1e1d1c1b1a1918171615141312100000000000000000000000000",
973+
"0xc0d0e0ff1e1d1c1b1a191817161514131210000000000000000000000000000",
974+
"0xd0e0ff1e1d1c1b1a19181716151413121000000000000000000000000000000",
975+
"0xe0ff1e1d1c1b1a1918171615141312100000000000000000000000000000000",
976+
"0xff1e1d1c1b1a191817161514131210000000000000000000000000000000000",
977+
"0xf1e1d1c1b1a19181716151413121000000000000000000000000000000000000",
978+
"0xe1d1c1b1a1918171615141312100000000000000000000000000000000000000",
979+
"0xd1c1b1a191817161514131210000000000000000000000000000000000000000",
980+
"0xc1b1a19181716151413121000000000000000000000000000000000000000000",
981+
"0xb1a1918171615141312100000000000000000000000000000000000000000000",
982+
"0xa191817161514131210000000000000000000000000000000000000000000000",
983+
"0x9181716151413121000000000000000000000000000000000000000000000000",
984+
"0x8171615141312100000000000000000000000000000000000000000000000000",
985+
"0x7161514131210000000000000000000000000000000000000000000000000000",
986+
"0x6151413121000000000000000000000000000000000000000000000000000000",
987+
"0x5141312100000000000000000000000000000000000000000000000000000000",
988+
"0x4131210000000000000000000000000000000000000000000000000000000000",
989+
"0x3121000000000000000000000000000000000000000000000000000000000000",
990+
"0x2100000000000000000000000000000000000000000000000000000000000000",
991+
"0x0",
992+
} {
993+
pc := new(uint64)
994+
*pc = uint64(i)
995+
push32(pc, nil, scope)
996+
res := scope.Stack.pop()
997+
if have := res.Hex(); have != want {
998+
t.Fatalf("case %d, have %v want %v", i, have, want)
999+
}
1000+
}
1001+
}

core/vm/interpreter.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
249249
} else if sLen > operation.maxStack {
250250
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
251251
}
252-
if !contract.UseGas(cost, in.evm.Config.Tracer, tracing.GasChangeIgnored) {
252+
// for tracing: this gas consumption event is emitted below in the debug section.
253+
if contract.Gas < cost {
253254
return nil, ErrOutOfGas
255+
} else {
256+
contract.Gas -= cost
254257
}
255258

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

286292
// Do tracing before memory expansion

core/vm/runtime/runtime_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,17 @@ func BenchmarkSimpleLoop(b *testing.B) {
514514
byte(vm.JUMP),
515515
}
516516

517+
loopingCode2 := []byte{
518+
byte(vm.JUMPDEST), // [ count ]
519+
// push args for the call
520+
byte(vm.PUSH4), 1, 2, 3, 4,
521+
byte(vm.PUSH5), 1, 2, 3, 4, 5,
522+
523+
byte(vm.POP), byte(vm.POP),
524+
byte(vm.PUSH6), 0, 0, 0, 0, 0, 0, // jumpdestination
525+
byte(vm.JUMP),
526+
}
527+
517528
callRevertingContractWithInput := []byte{
518529
byte(vm.JUMPDEST), //
519530
// push args for the call
@@ -540,6 +551,7 @@ func BenchmarkSimpleLoop(b *testing.B) {
540551
benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", "", b)
541552
benchmarkNonModifyingCode(100000000, callIdentity, "call-identity-100M", "", b)
542553
benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", "", b)
554+
benchmarkNonModifyingCode(100000000, loopingCode2, "loop2-100M", "", b)
543555
benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", "", b)
544556
benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", "", b)
545557
benchmarkNonModifyingCode(100000000, callRevertingContractWithInput, "call-reverting-100M", "", b)

0 commit comments

Comments
 (0)