From 32deb62ca82f2c932485740503ab5af5328071ed Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 27 Feb 2017 00:32:24 +0100 Subject: [PATCH] common/math: optimize PaddedBigBytes, use it more MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit name old time/op new time/op delta PaddedBigBytes-8 71.1ns ± 5% 46.1ns ± 1% -35.15% (p=0.000 n=20+19) name old alloc/op new alloc/op delta PaddedBigBytes-8 48.0B ± 0% 32.0B ± 0% -33.33% (p=0.000 n=20+20) --- accounts/abi/numbers.go | 2 +- accounts/abi/packing.go | 5 ++-- accounts/keystore/keystore_passphrase.go | 4 +-- common/math/big.go | 32 ++++++++++++++++++------ common/math/big_test.go | 7 ++++++ core/vm/instructions.go | 3 ++- core/vm/logger.go | 3 ++- crypto/signature_cgo.go | 4 +-- internal/ethapi/api.go | 2 +- tests/util.go | 2 +- 10 files changed, 45 insertions(+), 19 deletions(-) diff --git a/accounts/abi/numbers.go b/accounts/abi/numbers.go index 36fb6705e6..10afa6511f 100644 --- a/accounts/abi/numbers.go +++ b/accounts/abi/numbers.go @@ -59,7 +59,7 @@ var ( // U256 converts a big Int into a 256bit EVM number. func U256(n *big.Int) []byte { - return common.LeftPadBytes(math.U256(n).Bytes(), 32) + return math.PaddedBigBytes(math.U256(n), 32) } // packNum packs the given number (using the reflect value) and will cast it to appropriate number representation diff --git a/accounts/abi/packing.go b/accounts/abi/packing.go index 5054dcf138..1d7f85e2ba 100644 --- a/accounts/abi/packing.go +++ b/accounts/abi/packing.go @@ -20,6 +20,7 @@ import ( "reflect" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" ) // packBytesSlice packs the given bytes as [L, V] as the canonical representation @@ -45,9 +46,9 @@ func packElement(t Type, reflectValue reflect.Value) []byte { return common.LeftPadBytes(reflectValue.Bytes(), 32) case BoolTy: if reflectValue.Bool() { - return common.LeftPadBytes(common.Big1.Bytes(), 32) + return math.PaddedBigBytes(common.Big1, 32) } else { - return common.LeftPadBytes(common.Big0.Bytes(), 32) + return math.PaddedBigBytes(common.Big0, 32) } case BytesTy: if reflectValue.Kind() == reflect.Array { diff --git a/accounts/keystore/keystore_passphrase.go b/accounts/keystore/keystore_passphrase.go index 8ef510fcfa..2eae258415 100644 --- a/accounts/keystore/keystore_passphrase.go +++ b/accounts/keystore/keystore_passphrase.go @@ -36,6 +36,7 @@ import ( "path/filepath" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/randentropy" "github.com/pborman/uuid" @@ -115,8 +116,7 @@ func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) { return nil, err } encryptKey := derivedKey[:16] - keyBytes0 := crypto.FromECDSA(key.PrivateKey) - keyBytes := common.LeftPadBytes(keyBytes0, 32) + keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32) iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16 cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv) diff --git a/common/math/big.go b/common/math/big.go index c0508c102d..072ee09938 100644 --- a/common/math/big.go +++ b/common/math/big.go @@ -28,6 +28,13 @@ var ( MaxBig256 = new(big.Int).Set(tt256m1) ) +const ( + // number of bits in a big.Word + wordBits = 32 << (uint64(^big.Word(0)) >> 63) + // number of bytes in a big.Word + wordBytes = wordBits / 8 +) + // ParseBig256 parses s as a 256 bit integer in decimal or hexadecimal syntax. // Leading zeros are accepted. The empty string parses as zero. func ParseBig256(s string) (*big.Int, bool) { @@ -91,12 +98,24 @@ func FirstBitSet(v *big.Int) int { // PaddedBigBytes encodes a big integer as a big-endian byte slice. The length // of the slice is at least n bytes. func PaddedBigBytes(bigint *big.Int, n int) []byte { - bytes := bigint.Bytes() - if len(bytes) >= n { - return bytes + if bigint.BitLen()/8 >= n { + return bigint.Bytes() } ret := make([]byte, n) - return append(ret[:len(ret)-len(bytes)], bytes...) + readBits(ret, bigint.Bits()) + return ret +} + +// reads little-endian words as big-endian bytes into buf. +func readBits(buf []byte, words []big.Word) { + i := len(buf) + for _, d := range words { + for j := 0; j < wordBytes && i > 0; j++ { + i-- + buf[i] = byte(d) + d >>= 8 + } + } } // U256 encodes as a 256 bit two's complement number. This operation is destructive. @@ -119,9 +138,6 @@ func S256(x *big.Int) *big.Int { } } -// wordSize is the size number of bits in a big.Word. -const wordSize = 32 << (uint64(^big.Word(0)) >> 63) - // Exp implements exponentiation by squaring. // Exp returns a newly-allocated big integer and does not change // base or exponent. The result is truncated to 256 bits. @@ -131,7 +147,7 @@ func Exp(base, exponent *big.Int) *big.Int { result := big.NewInt(1) for _, word := range exponent.Bits() { - for i := 0; i < wordSize; i++ { + for i := 0; i < wordBits; i++ { if word&1 == 1 { U256(result.Mul(result, base)) } diff --git a/common/math/big_test.go b/common/math/big_test.go index a0f48a8ebf..bf3ba58e0a 100644 --- a/common/math/big_test.go +++ b/common/math/big_test.go @@ -131,6 +131,13 @@ func TestPaddedBigBytes(t *testing.T) { } } +func BenchmarkPaddedBigBytes(b *testing.B) { + bigint := MustParseBig256("123456789123456789123456789123456789") + for i := 0; i < b.N; i++ { + PaddedBigBytes(bigint, 32) + } +} + func TestU256(t *testing.T) { tests := []struct{ x, y *big.Int }{ {x: big.NewInt(0), y: big.NewInt(0)}, diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 8b7bcc4d61..ecef7e6a0d 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -252,10 +252,11 @@ func opXor(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stac evm.interpreter.intPool.put(y) return nil, nil } + func opByte(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { th, val := stack.pop(), stack.pop() if th.Cmp(big.NewInt(32)) < 0 { - byte := evm.interpreter.intPool.get().SetInt64(int64(common.LeftPadBytes(val.Bytes(), 32)[th.Int64()])) + byte := evm.interpreter.intPool.get().SetInt64(int64(math.PaddedBigBytes(val, 32)[th.Int64()])) stack.push(byte) } else { stack.push(new(big.Int)) diff --git a/core/vm/logger.go b/core/vm/logger.go index 3845b1073d..102a215c7f 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -23,6 +23,7 @@ import ( "unicode" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" ) type Storage map[common.Hash]common.Hash @@ -180,7 +181,7 @@ func StdErrFormat(logs []StructLog) { fmt.Fprintln(os.Stderr, "STACK =", len(log.Stack)) for i := len(log.Stack) - 1; i >= 0; i-- { - fmt.Fprintf(os.Stderr, "%04d: %x\n", len(log.Stack)-i-1, common.LeftPadBytes(log.Stack[i].Bytes(), 32)) + fmt.Fprintf(os.Stderr, "%04d: %x\n", len(log.Stack)-i-1, math.PaddedBigBytes(log.Stack[i], 32)) } const maxMem = 10 diff --git a/crypto/signature_cgo.go b/crypto/signature_cgo.go index 5faa6061fd..d1485de08c 100644 --- a/crypto/signature_cgo.go +++ b/crypto/signature_cgo.go @@ -23,7 +23,7 @@ import ( "crypto/elliptic" "fmt" - "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto/secp256k1" ) @@ -53,7 +53,7 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) { if len(hash) != 32 { return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(hash)) } - seckey := common.LeftPadBytes(prv.D.Bytes(), prv.Params().BitSize/8) + seckey := math.PaddedBigBytes(prv.D, prv.Params().BitSize/8) defer zeroBytes(seckey) return secp256k1.Sign(hash, seckey) } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 8f4bde4719..6a38272c13 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -720,7 +720,7 @@ func FormatLogs(structLogs []vm.StructLog) []StructLogRes { } for i, stackValue := range trace.Stack { - formattedStructLogs[index].Stack[i] = fmt.Sprintf("%x", common.LeftPadBytes(stackValue.Bytes(), 32)) + formattedStructLogs[index].Stack[i] = fmt.Sprintf("%x", math.PaddedBigBytes(stackValue, 32)) } for i := 0; i+32 <= len(trace.Memory); i += 32 { diff --git a/tests/util.go b/tests/util.go index ce5b02fed8..57f8714e9a 100644 --- a/tests/util.go +++ b/tests/util.go @@ -71,7 +71,7 @@ func checkLogs(tlog []Log, logs []*types.Log) error { } } } - genBloom := common.LeftPadBytes(types.LogsBloom([]*types.Log{logs[i]}).Bytes(), 256) + genBloom := math.PaddedBigBytes(types.LogsBloom([]*types.Log{logs[i]}), 256) if !bytes.Equal(genBloom, common.Hex2Bytes(log.BloomF)) { return fmt.Errorf("bloom mismatch")