From 198028d627952366d535024cf6fc4a4de07f5db7 Mon Sep 17 00:00:00 2001 From: greatroar <61184462+greatroar@users.noreply.github.com> Date: Sun, 26 Sep 2021 12:15:39 +0200 Subject: [PATCH] lib/rand: Optimizations (#7964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rand.secureSource.Uint64 no longer allocates. rand.String uses a strings.Builder. Benchmark results on linux/amd64: name old time/op new time/op delta SecureSource-8 69.1ns ± 3% 51.7ns ± 3% -25.21% (p=0.000 n=20+10) String-8 2.66µs ± 2% 1.95µs ± 1% -26.61% (p=0.000 n=10+10) name old alloc/op new alloc/op delta SecureSource-8 8.00B ± 0% 0.00B -100.00% (p=0.000 n=20+10) String-8 288B ± 0% 32B ± 0% -88.89% (p=0.000 n=10+10) name old allocs/op new allocs/op delta SecureSource-8 1.00 ± 0% 0.00 -100.00% (p=0.000 n=20+10) String-8 33.0 ± 0% 1.0 ± 0% -96.97% (p=0.000 n=10+10) --- lib/rand/random.go | 11 +++++++---- lib/rand/random_test.go | 7 +++++++ lib/rand/securesource.go | 12 +++++------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/rand/random.go b/lib/rand/random.go index 98ff76f53..d596aec12 100644 --- a/lib/rand/random.go +++ b/lib/rand/random.go @@ -12,6 +12,7 @@ import ( "io" mathRand "math/rand" "reflect" + "strings" ) // Reader is the standard crypto/rand.Reader with added buffering. @@ -37,11 +38,13 @@ var ( // (taken from randomCharset) of the specified length. The returned string // contains ~5.8 bits of entropy per character, due to the character set used. func String(l int) string { - bs := make([]byte, l) - for i := range bs { - bs[i] = randomCharset[defaultSecureRand.Intn(len(randomCharset))] + var sb strings.Builder + sb.Grow(l) + + for i := 0; i < l; i++ { + sb.WriteByte(randomCharset[defaultSecureRand.Intn(len(randomCharset))]) } - return string(bs) + return sb.String() } // Int63 returns a cryptographically secure random int63. diff --git a/lib/rand/random_test.go b/lib/rand/random_test.go index 4a19eb828..24569caf5 100644 --- a/lib/rand/random_test.go +++ b/lib/rand/random_test.go @@ -44,3 +44,10 @@ func TestRandomUint64(t *testing.T) { } } } + +func BenchmarkString(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + String(32) + } +} diff --git a/lib/rand/securesource.go b/lib/rand/securesource.go index bc2c87b85..caa1e2c42 100644 --- a/lib/rand/securesource.go +++ b/lib/rand/securesource.go @@ -21,6 +21,7 @@ import ( type secureSource struct { rd io.Reader mut sync.Mutex + buf [8]byte } func newSecureSource() *secureSource { @@ -47,18 +48,15 @@ func (s *secureSource) Read(p []byte) (int, error) { } func (s *secureSource) Uint64() uint64 { - var buf [8]byte - // Read eight bytes of entropy from the buffered, secure random number // generator. The buffered reader isn't concurrency safe, so we lock // around that. s.mut.Lock() - _, err := io.ReadFull(s.rd, buf[:]) - s.mut.Unlock() + defer s.mut.Unlock() + + _, err := io.ReadFull(s.rd, s.buf[:]) if err != nil { panic("randomness failure: " + err.Error()) } - - // Grab those bytes as an uint64 - return binary.BigEndian.Uint64(buf[:]) + return binary.LittleEndian.Uint64(s.buf[:]) }