core/vm: improved stack swap performance (#30249)

This PR adds the methods `Stack.swap1..16()` that faster than `Stack.swap(1..16)`. 

Co-authored-by: lmittmann <lmittmann@users.noreply.github.com>
pull/30270/head
lmittmann 1 month ago committed by GitHub
parent e9981bc6f7
commit b37ac5c102
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 90
      core/vm/instructions.go
  2. 32
      core/vm/jump_table.go
  3. 29
      core/vm/runtime/runtime_test.go
  4. 51
      core/vm/stack.go

@ -583,6 +583,86 @@ func opGas(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte
return nil, nil
}
func opSwap1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap1()
return nil, nil
}
func opSwap2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap2()
return nil, nil
}
func opSwap3(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap3()
return nil, nil
}
func opSwap4(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap4()
return nil, nil
}
func opSwap5(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap5()
return nil, nil
}
func opSwap6(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap6()
return nil, nil
}
func opSwap7(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap7()
return nil, nil
}
func opSwap8(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap8()
return nil, nil
}
func opSwap9(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap9()
return nil, nil
}
func opSwap10(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap10()
return nil, nil
}
func opSwap11(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap11()
return nil, nil
}
func opSwap12(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap12()
return nil, nil
}
func opSwap13(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap13()
return nil, nil
}
func opSwap14(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap14()
return nil, nil
}
func opSwap15(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap15()
return nil, nil
}
func opSwap16(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap16()
return nil, nil
}
func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
if interpreter.readOnly {
return nil, ErrWriteProtection
@ -923,13 +1003,3 @@ func makeDup(size int64) executionFunc {
return nil, nil
}
}
// make swap instruction function
func makeSwap(size int64) executionFunc {
// switch n + 1 otherwise n would be swapped with n
size++
return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap(int(size))
return nil, nil
}
}

@ -892,97 +892,97 @@ func newFrontierInstructionSet() JumpTable {
maxStack: maxDupStack(16),
},
SWAP1: {
execute: makeSwap(1),
execute: opSwap1,
constantGas: GasFastestStep,
minStack: minSwapStack(2),
maxStack: maxSwapStack(2),
},
SWAP2: {
execute: makeSwap(2),
execute: opSwap2,
constantGas: GasFastestStep,
minStack: minSwapStack(3),
maxStack: maxSwapStack(3),
},
SWAP3: {
execute: makeSwap(3),
execute: opSwap3,
constantGas: GasFastestStep,
minStack: minSwapStack(4),
maxStack: maxSwapStack(4),
},
SWAP4: {
execute: makeSwap(4),
execute: opSwap4,
constantGas: GasFastestStep,
minStack: minSwapStack(5),
maxStack: maxSwapStack(5),
},
SWAP5: {
execute: makeSwap(5),
execute: opSwap5,
constantGas: GasFastestStep,
minStack: minSwapStack(6),
maxStack: maxSwapStack(6),
},
SWAP6: {
execute: makeSwap(6),
execute: opSwap6,
constantGas: GasFastestStep,
minStack: minSwapStack(7),
maxStack: maxSwapStack(7),
},
SWAP7: {
execute: makeSwap(7),
execute: opSwap7,
constantGas: GasFastestStep,
minStack: minSwapStack(8),
maxStack: maxSwapStack(8),
},
SWAP8: {
execute: makeSwap(8),
execute: opSwap8,
constantGas: GasFastestStep,
minStack: minSwapStack(9),
maxStack: maxSwapStack(9),
},
SWAP9: {
execute: makeSwap(9),
execute: opSwap9,
constantGas: GasFastestStep,
minStack: minSwapStack(10),
maxStack: maxSwapStack(10),
},
SWAP10: {
execute: makeSwap(10),
execute: opSwap10,
constantGas: GasFastestStep,
minStack: minSwapStack(11),
maxStack: maxSwapStack(11),
},
SWAP11: {
execute: makeSwap(11),
execute: opSwap11,
constantGas: GasFastestStep,
minStack: minSwapStack(12),
maxStack: maxSwapStack(12),
},
SWAP12: {
execute: makeSwap(12),
execute: opSwap12,
constantGas: GasFastestStep,
minStack: minSwapStack(13),
maxStack: maxSwapStack(13),
},
SWAP13: {
execute: makeSwap(13),
execute: opSwap13,
constantGas: GasFastestStep,
minStack: minSwapStack(14),
maxStack: maxSwapStack(14),
},
SWAP14: {
execute: makeSwap(14),
execute: opSwap14,
constantGas: GasFastestStep,
minStack: minSwapStack(15),
maxStack: maxSwapStack(15),
},
SWAP15: {
execute: makeSwap(15),
execute: opSwap15,
constantGas: GasFastestStep,
minStack: minSwapStack(16),
maxStack: maxSwapStack(16),
},
SWAP16: {
execute: makeSwap(16),
execute: opSwap16,
constantGas: GasFastestStep,
minStack: minSwapStack(17),
maxStack: maxSwapStack(17),

@ -212,6 +212,35 @@ func BenchmarkEVM_CREATE2_1200(bench *testing.B) {
benchmarkEVM_Create(bench, "5b5862124f80600080f5600152600056")
}
func BenchmarkEVM_SWAP1(b *testing.B) {
// returns a contract that does n swaps (SWAP1)
swapContract := func(n uint64) []byte {
contract := []byte{
byte(vm.PUSH0), // PUSH0
byte(vm.PUSH0), // PUSH0
}
for i := uint64(0); i < n; i++ {
contract = append(contract, byte(vm.SWAP1))
}
return contract
}
state, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
contractAddr := common.BytesToAddress([]byte("contract"))
b.Run("10k", func(b *testing.B) {
contractCode := swapContract(10_000)
state.SetCode(contractAddr, contractCode)
for i := 0; i < b.N; i++ {
_, _, err := Call(contractAddr, []byte{}, &Config{State: state})
if err != nil {
b.Fatal(err)
}
}
})
}
func fakeHeader(n uint64, parentHash common.Hash) *types.Header {
header := types.Header{
Coinbase: common.HexToAddress("0x00000000000000000000000000000000deadbeef"),

@ -30,7 +30,7 @@ var stackPool = sync.Pool{
// Stack is an object for basic stack operations. Items popped to the stack are
// expected to be changed and modified. stack does not take care of adding newly
// initialised objects.
// initialized objects.
type Stack struct {
data []uint256.Int
}
@ -64,8 +64,53 @@ func (st *Stack) len() int {
return len(st.data)
}
func (st *Stack) swap(n int) {
st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n]
func (st *Stack) swap1() {
st.data[st.len()-2], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-2]
}
func (st *Stack) swap2() {
st.data[st.len()-3], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-3]
}
func (st *Stack) swap3() {
st.data[st.len()-4], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-4]
}
func (st *Stack) swap4() {
st.data[st.len()-5], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-5]
}
func (st *Stack) swap5() {
st.data[st.len()-6], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-6]
}
func (st *Stack) swap6() {
st.data[st.len()-7], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-7]
}
func (st *Stack) swap7() {
st.data[st.len()-8], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-8]
}
func (st *Stack) swap8() {
st.data[st.len()-9], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-9]
}
func (st *Stack) swap9() {
st.data[st.len()-10], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-10]
}
func (st *Stack) swap10() {
st.data[st.len()-11], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-11]
}
func (st *Stack) swap11() {
st.data[st.len()-12], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-12]
}
func (st *Stack) swap12() {
st.data[st.len()-13], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-13]
}
func (st *Stack) swap13() {
st.data[st.len()-14], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-14]
}
func (st *Stack) swap14() {
st.data[st.len()-15], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-15]
}
func (st *Stack) swap15() {
st.data[st.len()-16], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-16]
}
func (st *Stack) swap16() {
st.data[st.len()-17], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-17]
}
func (st *Stack) dup(n int) {

Loading…
Cancel
Save