Skip to content

Commit 4e5d1f1

Browse files
gballetkaralabe
authored andcommitted
core/vm: reuse bigint pools across transactions (#17070)
* core/vm: A pool for int pools * core/vm: fix rebase issue * core/vm: push leftover stack items after execution, not before
1 parent d57e85e commit 4e5d1f1

File tree

4 files changed

+112
-2
lines changed

4 files changed

+112
-2
lines changed

core/vm/instructions_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
3636
stack = newstack()
3737
pc = uint64(0)
3838
)
39+
env.interpreter.intPool = poolOfIntPools.get()
3940
for i, test := range tests {
4041
x := new(big.Int).SetBytes(common.Hex2Bytes(test.x))
4142
shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y))
@@ -64,13 +65,15 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
6465
}
6566
}
6667
}
68+
poolOfIntPools.put(env.interpreter.intPool)
6769
}
6870

6971
func TestByteOp(t *testing.T) {
7072
var (
7173
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
7274
stack = newstack()
7375
)
76+
env.interpreter.intPool = poolOfIntPools.get()
7477
tests := []struct {
7578
v string
7679
th uint64
@@ -97,6 +100,7 @@ func TestByteOp(t *testing.T) {
97100
t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.v, test.th, test.expected, actual)
98101
}
99102
}
103+
poolOfIntPools.put(env.interpreter.intPool)
100104
}
101105

102106
func TestSHL(t *testing.T) {
@@ -432,6 +436,7 @@ func TestOpMstore(t *testing.T) {
432436
stack = newstack()
433437
mem = NewMemory()
434438
)
439+
env.interpreter.intPool = poolOfIntPools.get()
435440
mem.Resize(64)
436441
pc := uint64(0)
437442
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
@@ -445,6 +450,7 @@ func TestOpMstore(t *testing.T) {
445450
if common.Bytes2Hex(mem.Get(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
446451
t.Fatalf("Mstore failed to overwrite previous value")
447452
}
453+
poolOfIntPools.put(env.interpreter.intPool)
448454
}
449455

450456
func BenchmarkOpMstore(bench *testing.B) {

core/vm/interpreter.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
7777
evm: evm,
7878
cfg: cfg,
7979
gasTable: evm.ChainConfig().GasTable(evm.BlockNumber),
80-
intPool: newIntPool(),
8180
}
8281
}
8382

@@ -104,6 +103,14 @@ func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack
104103
// considered a revert-and-consume-all-gas operation except for
105104
// errExecutionReverted which means revert-and-keep-gas-left.
106105
func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
106+
if in.intPool == nil {
107+
in.intPool = poolOfIntPools.get()
108+
defer func() {
109+
poolOfIntPools.put(in.intPool)
110+
in.intPool = nil
111+
}()
112+
}
113+
107114
// Increment the call depth which is restricted to 1024
108115
in.evm.depth++
109116
defer func() { in.evm.depth-- }()
@@ -133,6 +140,9 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
133140
)
134141
contract.Input = input
135142

143+
// Reclaim the stack as an int pool when the execution stops
144+
defer func() { in.intPool.put(stack.data...) }()
145+
136146
if in.cfg.Debug {
137147
defer func() {
138148
if err != nil {

core/vm/intpool.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
package vm
1818

19-
import "math/big"
19+
import (
20+
"math/big"
21+
"sync"
22+
)
2023

2124
var checkVal = big.NewInt(-42)
2225

@@ -65,3 +68,39 @@ func (p *intPool) put(is ...*big.Int) {
6568
p.pool.push(i)
6669
}
6770
}
71+
72+
// The intPool pool's default capacity
73+
const poolDefaultCap = 25
74+
75+
// intPoolPool manages a pool of intPools.
76+
type intPoolPool struct {
77+
pools []*intPool
78+
lock sync.Mutex
79+
}
80+
81+
var poolOfIntPools = &intPoolPool{
82+
pools: make([]*intPool, 0, poolDefaultCap),
83+
}
84+
85+
// get is looking for an available pool to return.
86+
func (ipp *intPoolPool) get() *intPool {
87+
ipp.lock.Lock()
88+
defer ipp.lock.Unlock()
89+
90+
if len(poolOfIntPools.pools) > 0 {
91+
ip := ipp.pools[len(ipp.pools)-1]
92+
ipp.pools = ipp.pools[:len(ipp.pools)-1]
93+
return ip
94+
}
95+
return newIntPool()
96+
}
97+
98+
// put a pool that has been allocated with get.
99+
func (ipp *intPoolPool) put(ip *intPool) {
100+
ipp.lock.Lock()
101+
defer ipp.lock.Unlock()
102+
103+
if len(ipp.pools) < cap(ipp.pools) {
104+
ipp.pools = append(ipp.pools, ip)
105+
}
106+
}

core/vm/intpool_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2018 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package vm
18+
19+
import (
20+
"testing"
21+
)
22+
23+
func TestIntPoolPoolGet(t *testing.T) {
24+
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
25+
26+
nip := poolOfIntPools.get()
27+
if nip == nil {
28+
t.Fatalf("Invalid pool allocation")
29+
}
30+
}
31+
32+
func TestIntPoolPoolPut(t *testing.T) {
33+
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
34+
35+
nip := poolOfIntPools.get()
36+
if len(poolOfIntPools.pools) != 0 {
37+
t.Fatalf("Pool got added to list when none should have been")
38+
}
39+
40+
poolOfIntPools.put(nip)
41+
if len(poolOfIntPools.pools) == 0 {
42+
t.Fatalf("Pool did not get added to list when one should have been")
43+
}
44+
}
45+
46+
func TestIntPoolPoolReUse(t *testing.T) {
47+
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
48+
nip := poolOfIntPools.get()
49+
poolOfIntPools.put(nip)
50+
poolOfIntPools.get()
51+
52+
if len(poolOfIntPools.pools) != 0 {
53+
t.Fatalf("Invalid number of pools. Got %d, expected %d", len(poolOfIntPools.pools), 0)
54+
}
55+
}

0 commit comments

Comments
 (0)