From 4e5d1f1c39159de42511770bd390ad583ebd57a5 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Tue, 3 Jul 2018 06:06:42 -0400 Subject: 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 --- core/vm/instructions_test.go | 6 +++++ core/vm/interpreter.go | 12 +++++++++- core/vm/intpool.go | 41 ++++++++++++++++++++++++++++++++- core/vm/intpool_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 core/vm/intpool_test.go (limited to 'core/vm') diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index f51e6363f..1643da967 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -36,6 +36,7 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64 stack = newstack() pc = uint64(0) ) + env.interpreter.intPool = poolOfIntPools.get() for i, test := range tests { x := new(big.Int).SetBytes(common.Hex2Bytes(test.x)) 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) { @@ -71,6 +73,7 @@ func TestByteOp(t *testing.T) { env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) stack = newstack() ) + env.interpreter.intPool = poolOfIntPools.get() tests := []struct { v string 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) } } + poolOfIntPools.put(env.interpreter.intPool) } func TestSHL(t *testing.T) { @@ -432,6 +436,7 @@ func TestOpMstore(t *testing.T) { stack = newstack() mem = NewMemory() ) + env.interpreter.intPool = poolOfIntPools.get() mem.Resize(64) pc := uint64(0) v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" @@ -445,6 +450,7 @@ func TestOpMstore(t *testing.T) { if common.Bytes2Hex(mem.Get(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } + poolOfIntPools.put(env.interpreter.intPool) } func BenchmarkOpMstore(bench *testing.B) { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 7090e0261..0d6038cbb 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -77,7 +77,6 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter { evm: evm, cfg: cfg, 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 // errExecutionReverted which means revert-and-keep-gas-left. 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 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 + // Reclaim the stack as an int pool when the execution stops + defer func() { in.intPool.put(stack.data...) }() + if in.cfg.Debug { defer func() { if err != nil { diff --git a/core/vm/intpool.go b/core/vm/intpool.go index 5dbda18ee..917a78d56 100644 --- a/core/vm/intpool.go +++ b/core/vm/intpool.go @@ -16,7 +16,10 @@ package vm -import "math/big" +import ( + "math/big" + "sync" +) var checkVal = big.NewInt(-42) @@ -65,3 +68,39 @@ func (p *intPool) put(is ...*big.Int) { 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) + } +} diff --git a/core/vm/intpool_test.go b/core/vm/intpool_test.go new file mode 100644 index 000000000..6c0d00f3c --- /dev/null +++ b/core/vm/intpool_test.go @@ -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 . + +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) + } +} -- cgit v1.2.3