unmergeable: benchmarking #21207

By June 15, 2020Ethereum
Click here to view original web page at github.com

This is not actually a PR, but an analysis of the cost of calling precompiles, due to EIP-2046 to lower the cost of calling precompiles.

It's based on benchmarking the following two loops, which both loops until OOG:

prog1

Code which calls the identity precompile with zero arguments:

		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),

prog2

And code which doesn't do the call, but instead just pops the arguments off the stack:

		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),

Current

With the current cost of 700, the prog1 is obviously a lot faster, burning through
100M gas in 120ms as opposed to 730ms:

BenchmarkSimpleLoop/identity-precompile-100M-6         	       9	 120120165 ns/op	42914516 B/op	  670317 allocs/op
BenchmarkSimpleLoop/loop-100M-6                        	       2	 732444458 ns/op	   14044 B/op	      42 allocs/op

With the proposed 40 gas:

BenchmarkSimpleLoop/identity-precompile-100M-6         	       1	1404082804 ns/op	593559048 B/op	 5814130 allocs/op
BenchmarkSimpleLoop/loop-100M-6                        	       2	 657346380 ns/op	   14044 B/op	      42 allocs/op

It's actually a factor 2 too slow. This means that we lowered the gas too much!

Testing with 100 gives a pretty close match:

--- a/params/protocol_params.go
+++ b/params/protocol_params.go
@@ -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)

This gives a pretty good match:

BenchmarkSimpleLoop/identity-precompile-100M-6         	       2	 617293453 ns/op	219197308 B/op	 3424734 allocs/op
BenchmarkSimpleLoop/loop-100M-6                        	       2	 677941768 ns/op	   14044 B/op	      42 allocs/op

Note though, that this includes a cost of 15 + 1 for the call to identity -- the cost of the input. So, let's try
with that cost set to zero, and the precompile doing nothing.

This makes the match even better:

BenchmarkSimpleLoop/identity-precompile-100M
BenchmarkSimpleLoop/identity-precompile-100M-6         	       2	 674733768 ns/op	244293824 B/op	 3816870 allocs/op
BenchmarkSimpleLoop/loop-100M
BenchmarkSimpleLoop/loop-100M-6                        	       2	 666473308 ns/op	   13508 B/op	      41 allocs/op

So for go-ethereum, the most 'correct' value, with the current implementation, seems to be a cost of 100 for calling a precmpile.

It’s based on benchmarking the […]

Leave a Reply