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
This commit is contained in:
Guillaume Ballet 2018-07-03 06:06:42 -04:00 committed by Péter Szilágyi
parent d57e85ecc9
commit 4e5d1f1c39
4 changed files with 112 additions and 2 deletions

View File

@ -36,6 +36,7 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
stack = newstack() stack = newstack()
pc = uint64(0) pc = uint64(0)
) )
env.interpreter.intPool = poolOfIntPools.get()
for i, test := range tests { for i, test := range tests {
x := new(big.Int).SetBytes(common.Hex2Bytes(test.x)) x := new(big.Int).SetBytes(common.Hex2Bytes(test.x))
shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y)) shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y))
@ -64,6 +65,7 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
} }
} }
} }
poolOfIntPools.put(env.interpreter.intPool)
} }
func TestByteOp(t *testing.T) { func TestByteOp(t *testing.T) {
@ -71,6 +73,7 @@ func TestByteOp(t *testing.T) {
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
) )
env.interpreter.intPool = poolOfIntPools.get()
tests := []struct { tests := []struct {
v string v string
th uint64 th uint64
@ -97,6 +100,7 @@ func TestByteOp(t *testing.T) {
t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.v, test.th, test.expected, actual) t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.v, test.th, test.expected, actual)
} }
} }
poolOfIntPools.put(env.interpreter.intPool)
} }
func TestSHL(t *testing.T) { func TestSHL(t *testing.T) {
@ -432,6 +436,7 @@ func TestOpMstore(t *testing.T) {
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
) )
env.interpreter.intPool = poolOfIntPools.get()
mem.Resize(64) mem.Resize(64)
pc := uint64(0) pc := uint64(0)
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
@ -445,6 +450,7 @@ func TestOpMstore(t *testing.T) {
if common.Bytes2Hex(mem.Get(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { if common.Bytes2Hex(mem.Get(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
t.Fatalf("Mstore failed to overwrite previous value") t.Fatalf("Mstore failed to overwrite previous value")
} }
poolOfIntPools.put(env.interpreter.intPool)
} }
func BenchmarkOpMstore(bench *testing.B) { func BenchmarkOpMstore(bench *testing.B) {

View File

@ -77,7 +77,6 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
evm: evm, evm: evm,
cfg: cfg, cfg: cfg,
gasTable: evm.ChainConfig().GasTable(evm.BlockNumber), gasTable: evm.ChainConfig().GasTable(evm.BlockNumber),
intPool: newIntPool(),
} }
} }
@ -104,6 +103,14 @@ func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack
// considered a revert-and-consume-all-gas operation except for // considered a revert-and-consume-all-gas operation except for
// errExecutionReverted which means revert-and-keep-gas-left. // errExecutionReverted which means revert-and-keep-gas-left.
func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) { func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
if in.intPool == nil {
in.intPool = poolOfIntPools.get()
defer func() {
poolOfIntPools.put(in.intPool)
in.intPool = nil
}()
}
// Increment the call depth which is restricted to 1024 // Increment the call depth which is restricted to 1024
in.evm.depth++ in.evm.depth++
defer func() { in.evm.depth-- }() defer func() { in.evm.depth-- }()
@ -133,6 +140,9 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
) )
contract.Input = input contract.Input = input
// Reclaim the stack as an int pool when the execution stops
defer func() { in.intPool.put(stack.data...) }()
if in.cfg.Debug { if in.cfg.Debug {
defer func() { defer func() {
if err != nil { if err != nil {

View File

@ -16,7 +16,10 @@
package vm package vm
import "math/big" import (
"math/big"
"sync"
)
var checkVal = big.NewInt(-42) var checkVal = big.NewInt(-42)
@ -65,3 +68,39 @@ func (p *intPool) put(is ...*big.Int) {
p.pool.push(i) p.pool.push(i)
} }
} }
// The intPool pool's default capacity
const poolDefaultCap = 25
// intPoolPool manages a pool of intPools.
type intPoolPool struct {
pools []*intPool
lock sync.Mutex
}
var poolOfIntPools = &intPoolPool{
pools: make([]*intPool, 0, poolDefaultCap),
}
// get is looking for an available pool to return.
func (ipp *intPoolPool) get() *intPool {
ipp.lock.Lock()
defer ipp.lock.Unlock()
if len(poolOfIntPools.pools) > 0 {
ip := ipp.pools[len(ipp.pools)-1]
ipp.pools = ipp.pools[:len(ipp.pools)-1]
return ip
}
return newIntPool()
}
// put a pool that has been allocated with get.
func (ipp *intPoolPool) put(ip *intPool) {
ipp.lock.Lock()
defer ipp.lock.Unlock()
if len(ipp.pools) < cap(ipp.pools) {
ipp.pools = append(ipp.pools, ip)
}
}

55
core/vm/intpool_test.go Normal file
View File

@ -0,0 +1,55 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package vm
import (
"testing"
)
func TestIntPoolPoolGet(t *testing.T) {
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
nip := poolOfIntPools.get()
if nip == nil {
t.Fatalf("Invalid pool allocation")
}
}
func TestIntPoolPoolPut(t *testing.T) {
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
nip := poolOfIntPools.get()
if len(poolOfIntPools.pools) != 0 {
t.Fatalf("Pool got added to list when none should have been")
}
poolOfIntPools.put(nip)
if len(poolOfIntPools.pools) == 0 {
t.Fatalf("Pool did not get added to list when one should have been")
}
}
func TestIntPoolPoolReUse(t *testing.T) {
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
nip := poolOfIntPools.get()
poolOfIntPools.put(nip)
poolOfIntPools.get()
if len(poolOfIntPools.pools) != 0 {
t.Fatalf("Invalid number of pools. Got %d, expected %d", len(poolOfIntPools.pools), 0)
}
}