From: isharipo Date: Tue, 6 Mar 2018 06:07:34 +0000 (+0300) Subject: math/big: speedup nat.setBytes for bigger slices X-Git-Tag: go1.11beta1~1284 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=d2a5263a9cb93ab7381d31e1c0494ade9e50b9ab;p=gostls13.git math/big: speedup nat.setBytes for bigger slices Set up to _S (number of bytes in Uint) bytes at time by using BigEndian.Uint32 and BigEndian.Uint64. The performance improves for slices bigger than _S bytes. This is the case for 128/256bit arith that initializes it's objects from bytes. name old time/op new time/op delta NatSetBytes/8-4 29.8ns ± 1% 11.4ns ± 0% -61.63% (p=0.000 n=9+8) NatSetBytes/24-4 109ns ± 1% 56ns ± 0% -48.75% (p=0.000 n=9+8) NatSetBytes/128-4 420ns ± 2% 110ns ± 1% -73.83% (p=0.000 n=10+10) NatSetBytes/7-4 26.2ns ± 1% 21.3ns ± 2% -18.63% (p=0.000 n=8+9) NatSetBytes/23-4 106ns ± 1% 67ns ± 1% -36.93% (p=0.000 n=9+10) NatSetBytes/127-4 410ns ± 2% 121ns ± 0% -70.46% (p=0.000 n=9+8) Found this optimization opportunity by looking at ethereum_corevm community benchmark cpuprofile. name old time/op new time/op delta OpDiv256-4 715ns ± 1% 596ns ± 1% -16.57% (p=0.008 n=5+5) OpDiv128-4 373ns ± 1% 314ns ± 1% -15.83% (p=0.008 n=5+5) OpDiv64-4 301ns ± 0% 285ns ± 1% -5.12% (p=0.008 n=5+5) Change-Id: I8e5a680ae6284c8233d8d7431d51253a8a740b57 Reviewed-on: https://go-review.googlesource.com/98775 Run-TryBot: Iskander Sharipov Reviewed-by: Robert Griesemer TryBot-Result: Gobot Gobot --- diff --git a/src/cmd/compile/internal/gc/inl_test.go b/src/cmd/compile/internal/gc/inl_test.go index a937c15432..d1dc6fbbfa 100644 --- a/src/cmd/compile/internal/gc/inl_test.go +++ b/src/cmd/compile/internal/gc/inl_test.go @@ -133,6 +133,9 @@ func TestIntendedInlining(t *testing.T) { "regexp": { "(*bitState).push", }, + "math/big": { + "bigEndianWord", + }, } if runtime.GOARCH != "386" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" { diff --git a/src/math/big/nat.go b/src/math/big/nat.go index 3bb818f5f2..c1812e3544 100644 --- a/src/math/big/nat.go +++ b/src/math/big/nat.go @@ -9,6 +9,7 @@ package big import ( + "encoding/binary" "math/bits" "math/rand" "sync" @@ -1208,25 +1209,32 @@ func (z nat) bytes(buf []byte) (i int) { return } +// bigEndianWord returns the contents of buf interpreted as a big-endian encoded Word value. +func bigEndianWord(buf []byte) Word { + if _W == 64 { + return Word(binary.BigEndian.Uint64(buf)) + } else { // Explicit else is required to get inlining. See #23521 + return Word(binary.BigEndian.Uint32(buf)) + } +} + // setBytes interprets buf as the bytes of a big-endian unsigned // integer, sets z to that value, and returns z. func (z nat) setBytes(buf []byte) nat { z = z.make((len(buf) + _S - 1) / _S) - k := 0 - s := uint(0) - var d Word - for i := len(buf); i > 0; i-- { - d |= Word(buf[i-1]) << s - if s += 8; s == _S*8 { - z[k] = d - k++ - s = 0 - d = 0 - } + i := len(buf) + for k := 0; i >= _S; k++ { + z[k] = bigEndianWord(buf[i-_S : i]) + i -= _S } - if k < len(z) { - z[k] = d + if i > 0 { + var d Word + for s := uint(0); i > 0; s += 8 { + d |= Word(buf[i-1]) << s + i-- + } + z[len(z)-1] = d } return z.norm() diff --git a/src/math/big/nat_test.go b/src/math/big/nat_test.go index c25cdf00a3..9bb96b1157 100644 --- a/src/math/big/nat_test.go +++ b/src/math/big/nat_test.go @@ -665,3 +665,22 @@ func BenchmarkNatSqr(b *testing.B) { }) } } + +func BenchmarkNatSetBytes(b *testing.B) { + const maxLength = 128 + lengths := []int{ + // No remainder: + 8, 24, maxLength, + // With remainder: + 7, 23, maxLength - 1, + } + n := make(nat, maxLength/_W) // ensure n doesn't need to grow during the test + buf := make([]byte, maxLength) + for _, l := range lengths { + b.Run(fmt.Sprint(l), func(b *testing.B) { + for i := 0; i < b.N; i++ { + n.setBytes(buf[:l]) + } + }) + } +}