Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core/vm: reuse Memory instances #30137

Merged
merged 10 commits into from
Aug 20, 2024
4 changes: 2 additions & 2 deletions core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -871,14 +871,14 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)

func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
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
}

func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
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
return ret, ErrExecutionReverted
Expand Down
1 change: 1 addition & 0 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// they are returned to the pools
defer func() {
returnStack(stack)
mem.Free()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we have a func returnStack(s *Stack) already, it would be nicer to make them aligned, and have returnMemory(m *Memory).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NewMemory is public and since it uses the pool it would be nice to also have the free method available. Should we maybe change the stack to Free as well or switch to returnMemory and make NewMemory private?

}()
contract.Input = input

Expand Down
21 changes: 20 additions & 1 deletion core/vm/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@
package vm

import (
"sync"

"github.com/holiman/uint256"
)

var memoryPool = sync.Pool{
New: func() any {
return &Memory{}
},
}

// Memory implements a simple memory model for the ethereum virtual machine.
type Memory struct {
store []byte
Expand All @@ -28,7 +36,18 @@ type Memory struct {

// NewMemory returns a new memory model.
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
Expand Down
37 changes: 37 additions & 0 deletions core/vm/runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package runtime

import (
"encoding/binary"
"fmt"
"math/big"
"os"
"strconv"
"strings"
"testing"

Expand Down Expand Up @@ -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 {
header := types.Header{
Coinbase: common.HexToAddress("0x00000000000000000000000000000000deadbeef"),
Expand Down