core/vm: reuse Memory instances (#30137)

This PR adds a sync.Pool to reuse instances of Memory in EVMInterpreter.
This commit is contained in:
lmittmann 2024-08-20 14:31:06 +02:00 committed by GitHub
parent 3b48b16290
commit fc88cea648
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 60 additions and 3 deletions

@ -871,14 +871,14 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
offset, size := scope.Stack.pop(), scope.Stack.pop() offset, size := scope.Stack.pop(), scope.Stack.pop()
ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) ret := scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
return ret, errStopToken return ret, errStopToken
} }
func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
offset, size := scope.Stack.pop(), scope.Stack.pop() offset, size := scope.Stack.pop(), scope.Stack.pop()
ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) ret := scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
interpreter.returnData = ret interpreter.returnData = ret
return ret, ErrExecutionReverted return ret, ErrExecutionReverted

@ -198,6 +198,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// they are returned to the pools // they are returned to the pools
defer func() { defer func() {
returnStack(stack) returnStack(stack)
mem.Free()
}() }()
contract.Input = input contract.Input = input

@ -17,9 +17,17 @@
package vm package vm
import ( import (
"sync"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
var memoryPool = sync.Pool{
New: func() any {
return &Memory{}
},
}
// Memory implements a simple memory model for the ethereum virtual machine. // Memory implements a simple memory model for the ethereum virtual machine.
type Memory struct { type Memory struct {
store []byte store []byte
@ -28,7 +36,18 @@ type Memory struct {
// NewMemory returns a new memory model. // NewMemory returns a new memory model.
func NewMemory() *Memory { func NewMemory() *Memory {
return &Memory{} return memoryPool.Get().(*Memory)
}
// Free returns the memory to the pool.
func (m *Memory) Free() {
// To reduce peak allocation, return only smaller memory instances to the pool.
const maxBufferSize = 16 << 10
if cap(m.store) <= maxBufferSize {
m.store = m.store[:0]
m.lastGasCost = 0
memoryPool.Put(m)
}
} }
// Set sets offset + size to value // Set sets offset + size to value

@ -17,9 +17,11 @@
package runtime package runtime
import ( import (
"encoding/binary"
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
"strconv"
"strings" "strings"
"testing" "testing"
@ -241,6 +243,41 @@ func BenchmarkEVM_SWAP1(b *testing.B) {
}) })
} }
func BenchmarkEVM_RETURN(b *testing.B) {
// returns a contract that returns a zero-byte slice of len size
returnContract := func(size uint64) []byte {
contract := []byte{
byte(vm.PUSH8), 0, 0, 0, 0, 0, 0, 0, 0, // PUSH8 0xXXXXXXXXXXXXXXXX
byte(vm.PUSH0), // PUSH0
byte(vm.RETURN), // RETURN
}
binary.BigEndian.PutUint64(contract[1:], size)
return contract
}
state, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
contractAddr := common.BytesToAddress([]byte("contract"))
for _, n := range []uint64{1_000, 10_000, 100_000, 1_000_000} {
b.Run(strconv.FormatUint(n, 10), func(b *testing.B) {
b.ReportAllocs()
contractCode := returnContract(n)
state.SetCode(contractAddr, contractCode)
for i := 0; i < b.N; i++ {
ret, _, err := Call(contractAddr, []byte{}, &Config{State: state})
if err != nil {
b.Fatal(err)
}
if uint64(len(ret)) != n {
b.Fatalf("expected return size %d, got %d", n, len(ret))
}
}
})
}
}
func fakeHeader(n uint64, parentHash common.Hash) *types.Header { func fakeHeader(n uint64, parentHash common.Hash) *types.Header {
header := types.Header{ header := types.Header{
Coinbase: common.HexToAddress("0x00000000000000000000000000000000deadbeef"), Coinbase: common.HexToAddress("0x00000000000000000000000000000000deadbeef"),