core/vm: speed up stack

This commit is contained in:
MariusVanDerWijden 2025-05-08 11:33:02 +02:00
parent 06990c1544
commit 04023e6120
3 changed files with 43 additions and 26 deletions

View file

@ -991,13 +991,13 @@ func makePush(size uint64, pushByteSize int) executionFunc {
start = min(codeLen, int(*pc+1))
end = min(codeLen, start+pushByteSize)
)
a := new(uint256.Int).SetBytes(scope.Contract.Code[start:end])
a := scope.Stack.get()
a.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
}

View file

@ -24,6 +24,7 @@ import (
"os"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
@ -281,25 +282,40 @@ func TestJsonTestcases(t *testing.T) {
func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newStackForTesting()
scope = &ScopeContext{nil, stack, nil}
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newStackForTesting()
code = []byte{}
opPush32 = makePush(32, 32)
)
// convert args
intArgs := make([]*uint256.Int, len(args))
for i, arg := range args {
code = append(code, common.LeftPadBytes(common.Hex2Bytes(arg), 32)...)
intArgs[i] = new(uint256.Int).SetBytes(common.Hex2Bytes(arg))
}
pc := uint64(0)
scope := &ScopeContext{nil, stack, &Contract{Code: code}}
start := time.Now()
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
for _, arg := range intArgs {
stack.push(arg)
for range len(args) {
opPush32(&pc, evm.interpreter, scope)
pc += 32
}
op(&pc, evm.interpreter, scope)
stack.pop()
opPop(&pc, evm.interpreter, scope)
}
bench.StopTimer()
elapsed := uint64(time.Since(start))
if elapsed < 1 {
elapsed = 1
}
reqGas := uint64(len(args))*GasFastestStep + GasFastestStep + GasQuickStep
gasUsed := reqGas * uint64(bench.N)
bench.ReportMetric(float64(reqGas), "gas/op")
// Keep it as uint64, multiply 100 to get two digit float later
mgasps := (100 * 1000 * gasUsed) / elapsed
bench.ReportMetric(float64(mgasps)/100, "mgas/s")
for i, arg := range args {
want := new(uint256.Int).SetBytes(common.Hex2Bytes(arg))

View file

@ -29,28 +29,24 @@ type stackArena struct {
}
func (sa *stackArena) push(value *uint256.Int) {
if len(sa.data) <= sa.top {
// we need to grow the arena
sa.data = slices.Grow(sa.data, 512)
sa.data = sa.data[:cap(sa.data)]
}
sa.data[sa.top] = *value
sa.top++
}
func (sa *stackArena) pop() {
sa.top--
}
func newArena() *stackArena {
return &stackArena{
data: make([]uint256.Int, 1024),
data: make([]uint256.Int, 1025),
}
}
// stack returns an instance of a stack which uses the underlying arena. The instance
// must be released by invoking the (*Stack).release() method
func (sa *stackArena) stack() *Stack {
// make sure every substack has at least 1024 elements
if len(sa.data) <= sa.top+1024 {
// we need to grow the arena
sa.data = slices.Grow(sa.data, 1024)
sa.data = sa.data[:cap(sa.data)]
}
return &Stack{
bottom: sa.top,
size: 0,
@ -62,7 +58,7 @@ func (sa *stackArena) stack() *Stack {
// backed by a newly allocated arena.
func newStackForTesting() *Stack {
arena := &stackArena{
data: make([]uint256.Int, 256),
data: make([]uint256.Int, 1025),
}
return arena.stack()
}
@ -89,16 +85,22 @@ func (s *Stack) Data() []uint256.Int {
}
func (s *Stack) push(d *uint256.Int) {
// NOTE push limit (1024) is checked in baseCheck
s.inner.push(d)
s.inner.data[s.inner.top] = *d
s.inner.top++
s.size++
}
func (s *Stack) get() *uint256.Int {
elem := &s.inner.data[s.inner.top]
s.inner.top++
s.size++
return elem
}
func (s *Stack) pop() uint256.Int {
ret := s.inner.data[s.bottom+s.size-1]
s.inner.pop()
s.inner.top--
s.size--
return ret
return s.inner.data[s.inner.top]
}
func (s *Stack) len() int {
@ -155,7 +157,6 @@ func (s *Stack) swap16() {
}
func (s *Stack) dup(n int) {
// TODO: check size of inner
s.inner.data[s.bottom+s.size] = s.inner.data[s.bottom+s.size-n]
s.size++
s.inner.top++