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

unmergeable: benchmarking #21207

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,18 @@ var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{
}

// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.RequiredGas(input)
if contract.UseGas(gas) {
return p.Run(input)
// It returns
// - the returned bytes,
// - the _remaining_ gas,
// - any error that occurred
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
gasCost := p.RequiredGas(input)
if suppliedGas < gasCost {
return nil, 0, ErrOutOfGas
}
return nil, ErrOutOfGas
suppliedGas -= gasCost
output, err := p.Run(input)
return output, suppliedGas, err
}

// ECRECOVER implemented as a native contract.
Expand Down Expand Up @@ -187,16 +193,19 @@ type dataCopy struct{}
// This method does not require any overflow checking as the input size gas costs
// required for anything significant is so high it's impossible to pay for.
func (c *dataCopy) RequiredGas(input []byte) uint64 {
return 0
return uint64(len(input)+31)/32*params.IdentityPerWordGas + params.IdentityBaseGas
}
func (c *dataCopy) Run(in []byte) ([]byte, error) {
return in, nil
return nil, nil
//return in, nil
}

// bigModExp implements a native big integer exponential modular operation.
type bigModExp struct{}

var (
big0 = big.NewInt(0)
big1 = big.NewInt(1)
big4 = big.NewInt(4)
big8 = big.NewInt(8)
Expand Down
52 changes: 30 additions & 22 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,28 @@ type (
GetHashFunc func(uint64) common.Hash
)

func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract
if evm.chainRules.IsYoloV1 {
precompiles = PrecompiledContractsYoloV1
} else if evm.chainRules.IsIstanbul {
precompiles = PrecompiledContractsIstanbul
} else if evm.chainRules.IsByzantium {
precompiles = PrecompiledContractsByzantium
} else {
precompiles = PrecompiledContractsHomestead
}
p, ok := precompiles[addr]
return p, ok
}

// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
if contract.CodeAddr != nil {
precompiles := PrecompiledContractsHomestead
if evm.chainRules.IsByzantium {
precompiles = PrecompiledContractsByzantium
}
if evm.chainRules.IsIstanbul {
precompiles = PrecompiledContractsIstanbul
}
if evm.chainRules.IsYoloV1 {
precompiles = PrecompiledContractsYoloV1
}
if p := precompiles[*contract.CodeAddr]; p != nil {
return RunPrecompiledContract(p, input, contract)
if p, ok := evm.precompile(*contract.CodeAddr); ok {
ret, remaining, err := RunPrecompiledContract(p, input, contract.Gas)
contract.Gas = remaining
return ret, err
}
}
for _, interpreter := range evm.interpreters {
Expand Down Expand Up @@ -339,21 +346,22 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
var (
to = AccountRef(addr)
snapshot = evm.StateDB.Snapshot()
)
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, to, new(big.Int), gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))

snapshot := evm.StateDB.Snapshot()

Choose a reason for hiding this comment

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

As a snapshot is not used in RunPrecompiledContract below does it affect the performance?

// We do an AddBalance of zero here, just in order to trigger a touch.
// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
// but is the correct thing to do and matters on other networks, in tests, and potential
// future scenarios
evm.StateDB.AddBalance(addr, big.NewInt(0))
evm.StateDB.AddBalance(addr, big0)

if p, isPrecompile := evm.precompile(addr); isPrecompile {
return RunPrecompiledContract(p, input, gas)
}
var addrCopy common.Address
addrCopy.SetBytes(addr.Bytes())
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addr))
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in Homestead this also counts for code storage gas errors.
Expand Down
21 changes: 14 additions & 7 deletions core/vm/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,20 @@ type Config struct {
func setDefaults(cfg *Config) {
if cfg.ChainConfig == nil {
cfg.ChainConfig = &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: new(big.Int),
DAOForkBlock: new(big.Int),
DAOForkSupport: false,
EIP150Block: new(big.Int),
EIP155Block: new(big.Int),
EIP158Block: new(big.Int),
ChainID: big.NewInt(1),
HomesteadBlock: new(big.Int),
DAOForkBlock: new(big.Int),
DAOForkSupport: false,
EIP150Block: new(big.Int),
EIP150Hash: common.Hash{},
EIP155Block: new(big.Int),
EIP158Block: new(big.Int),
ByzantiumBlock: new(big.Int),
ConstantinopleBlock: new(big.Int),
PetersburgBlock: new(big.Int),
IstanbulBlock: new(big.Int),
MuirGlacierBlock: new(big.Int),
YoloV1Block: nil,
}
}

Expand Down
102 changes: 74 additions & 28 deletions core/vm/runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,34 +321,6 @@ func TestBlockhash(t *testing.T) {
}
}

// BenchmarkSimpleLoop test a pretty simple loop which loops
// 1M (1 048 575) times.
// Takes about 200 ms
func BenchmarkSimpleLoop(b *testing.B) {
// 0xfffff = 1048575 loops
code := []byte{
byte(vm.PUSH3), 0x0f, 0xff, 0xff,
byte(vm.JUMPDEST), // [ count ]
byte(vm.PUSH1), 1, // [count, 1]
byte(vm.SWAP1), // [1, count]
byte(vm.SUB), // [ count -1 ]
byte(vm.DUP1), // [ count -1 , count-1]
byte(vm.PUSH1), 4, // [count-1, count -1, label]
byte(vm.JUMPI), // [ 0 ]
byte(vm.STOP),
}
//tracer := vm.NewJSONLogger(nil, os.Stdout)
//Execute(code, nil, &Config{
// EVMConfig: vm.Config{
// Debug: true,
// Tracer: tracer,
// }})

for i := 0; i < b.N; i++ {
Execute(code, nil, nil)
}
}

type stepCounter struct {
inner *vm.JSONLogger
steps int
Expand Down Expand Up @@ -593,3 +565,77 @@ func DisabledTestEipExampleCases(t *testing.T) {
"allowed, and causes an error", code)
}
}

// benchmarkNonModifyingCode benchmarks code, but if the code modifies the
// state, this should not be used, since it does not reset the state between runs.
func benchmarkNonModifyingCode(gas uint64, code []byte, name string, b *testing.B) {
cfg := new(Config)
setDefaults(cfg)
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
cfg.GasLimit = gas
var (
destination = common.BytesToAddress([]byte("contract"))
vmenv = NewEnv(cfg)
sender = vm.AccountRef(cfg.Origin)
)
cfg.State.CreateAccount(destination)
//cfg.State.CreateAccount(cfg.Origin)
// set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(destination, code)

b.Run(name, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
vmenv.Call(sender, destination, nil, gas, cfg.Value)
}
})
}

// BenchmarkSimpleLoop test a pretty simple loop which loops until OOG
// 55 ms
func BenchmarkSimpleLoop(b *testing.B) {

precompileCallerCode := []byte{
byte(vm.JUMPDEST), // [ count ]
// push args for the call
byte(vm.PUSH1), 0, // out size
byte(vm.DUP1), // out offset
byte(vm.DUP1), // out insize
byte(vm.DUP1), // in offset
byte(vm.PUSH1), 0x4, // address of identity
byte(vm.GAS), // gas
byte(vm.STATICCALL),
byte(vm.POP), // pop return value
byte(vm.PUSH1), 0, // jumpdestination
byte(vm.JUMP),
}

loopingCode := []byte{
byte(vm.JUMPDEST), // [ count ]
// push args for the call
byte(vm.PUSH1), 0, // out size
byte(vm.DUP1), // out offset
byte(vm.DUP1), // out insize
byte(vm.DUP1), // in offset
byte(vm.PUSH1), 0x4, // address of identity
byte(vm.GAS), // gas

byte(vm.POP),byte(vm.POP),byte(vm.POP),byte(vm.POP),byte(vm.POP),byte(vm.POP),
byte(vm.PUSH1), 0, // jumpdestination
byte(vm.JUMP),
}

//tracer := vm.NewJSONLogger(nil, os.Stdout)
//Execute(loopingCode, nil, &Config{
// EVMConfig: vm.Config{
// Debug: true,
// Tracer: tracer,
// }})
// 100M gas
//benchmarkNonModifyingCode(100000000, precompileCallerCode, "identity-precompile-100M", b)
//benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", b)


benchmarkNonModifyingCode(10000000, precompileCallerCode, "identity-precompile-10M", b)
benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b)
}
2 changes: 1 addition & 1 deletion params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const (

// These have been changed during the course of the chain
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
CallGasEIP150 uint64 = 700 // Static portion of gas for CALL-derivates after EIP 150 (Tangerine)
CallGasEIP150 uint64 = 100 // Static portion of gas for CALL-derivates after EIP 150 (Tangerine)
BalanceGasFrontier uint64 = 20 // The cost of a BALANCE operation
BalanceGasEIP150 uint64 = 400 // The cost of a BALANCE operation after Tangerine
BalanceGasEIP1884 uint64 = 700 // The cost of a BALANCE operation after EIP 1884 (part of Istanbul)
Expand Down