Skip to content

Commit

Permalink
math/rand: use fastrand64 if possible
Browse files Browse the repository at this point in the history
Now that the top-level math/rand functions are auto-seeded by default
(issue #54880), use the runtime fastrand64 function when 1) Seed
has not been called; 2) the GODEBUG randautoseed=0 is not used.

The benchmarks run quickly and are relatively noisy, but they show
significant improvements for parallel calls to the top-level functions.

goos: linux
goarch: amd64
pkg: math/rand
cpu: 11th Gen Intel(R) Core(TM) i7-11850H @ 2.50GHz
                           │  /tmp/foo.1   │              /tmp/foo.2               │
                           │    sec/op     │    sec/op      vs base                │
Int63Threadsafe-16            11.605n ± 1%    3.094n ±  1%  -73.34% (p=0.000 n=10)
Int63ThreadsafeParallel-16   67.8350n ± 2%   0.4000n ±  1%  -99.41% (p=0.000 n=10)
Int63Unthreadsafe-16           1.947n ± 3%    1.924n ±  2%        ~ (p=0.189 n=10)
Intn1000-16                    4.295n ± 2%    4.287n ±  3%        ~ (p=0.517 n=10)
Int63n1000-16                  4.379n ± 0%    4.192n ±  2%   -4.27% (p=0.000 n=10)
Int31n1000-16                  3.641n ± 3%    3.506n ±  0%   -3.69% (p=0.000 n=10)
Float32-16                     3.330n ± 7%    3.250n ±  2%   -2.40% (p=0.017 n=10)
Float64-16                     2.194n ± 6%    2.056n ±  4%   -6.31% (p=0.004 n=10)
Perm3-16                       43.39n ± 9%    38.28n ± 12%  -11.77% (p=0.015 n=10)
Perm30-16                      324.4n ± 6%    315.9n ± 19%        ~ (p=0.315 n=10)
Perm30ViaShuffle-16            175.4n ± 1%    143.6n ±  2%  -18.15% (p=0.000 n=10)
ShuffleOverhead-16             223.4n ± 2%    215.8n ±  1%   -3.38% (p=0.000 n=10)
Read3-16                       5.428n ± 3%    5.406n ±  2%        ~ (p=0.780 n=10)
Read64-16                      41.55n ± 5%    40.14n ±  3%   -3.38% (p=0.000 n=10)
Read1000-16                    622.9n ± 4%    594.9n ±  2%   -4.50% (p=0.000 n=10)
Concurrent-16                136.300n ± 2%    4.647n ± 26%  -96.59% (p=0.000 n=10)
geomean                        23.40n         12.15n        -48.08%

Fixes #49892

Change-Id: Iba75b326145512ab0b7ece233b98ac3d4e1fb504
Reviewed-on: https://go-review.googlesource.com/c/go/+/465037
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
Auto-Submit: Ian Lance Taylor <iant@google.com>
  • Loading branch information
ianlancetaylor authored and gopherbot committed Feb 7, 2023
1 parent f63ee8b commit 0b9974d
Show file tree
Hide file tree
Showing 3 changed files with 284 additions and 44 deletions.
148 changes: 148 additions & 0 deletions src/math/rand/default_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package rand_test

import (
"fmt"
"internal/race"
"internal/testenv"
. "math/rand"
"os"
"runtime"
"strconv"
"sync"
"testing"
)

// Test that racy access to the default functions behaves reasonably.
func TestDefaultRace(t *testing.T) {
// Skip the test in short mode, but even in short mode run
// the test if we are using the race detector, because part
// of this is to see whether the race detector reports any problems.
if testing.Short() && !race.Enabled {
t.Skip("skipping starting another executable in short mode")
}

const env = "GO_RAND_TEST_HELPER_CODE"
if v := os.Getenv(env); v != "" {
doDefaultTest(t, v)
return
}

t.Parallel()

for i := 0; i < 6; i++ {
i := i
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Parallel()
exe, err := os.Executable()
if err != nil {
exe = os.Args[0]
}
cmd := testenv.Command(t, exe, "-test.run=TestDefaultRace")
cmd = testenv.CleanCmdEnv(cmd)
cmd.Env = append(cmd.Env, fmt.Sprintf("GO_RAND_TEST_HELPER_CODE=%d", i/2))
if i%2 != 0 {
cmd.Env = append(cmd.Env, "GODEBUG=randautoseed=0")
}
out, err := cmd.CombinedOutput()
if len(out) > 0 {
t.Logf("%s", out)
}
if err != nil {
t.Error(err)
}
})
}
}

// doDefaultTest should be run before there have been any calls to the
// top-level math/rand functions. Make sure that we can make concurrent
// calls to top-level functions and to Seed without any duplicate values.
// This will also give the race detector a change to report any problems.
func doDefaultTest(t *testing.T, v string) {
code, err := strconv.Atoi(v)
if err != nil {
t.Fatalf("internal error: unrecognized code %q", v)
}

goroutines := runtime.GOMAXPROCS(0)
if goroutines < 4 {
goroutines = 4
}

ch := make(chan uint64, goroutines*3)
var wg sync.WaitGroup

// The various tests below should not cause race detector reports
// and should not produce duplicate results.
//
// Note: these tests can theoretically fail when using fastrand64
// in that it is possible to coincidentally get the same random
// number twice. That could happen something like 1 / 2**64 times,
// which is rare enough that it may never happen. We don't worry
// about that case.

switch code {
case 0:
// Call Seed and Uint64 concurrently.
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func(s int64) {
defer wg.Done()
Seed(s)
}(int64(i) + 100)
}
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func() {
defer wg.Done()
ch <- Uint64()
}()
}
case 1:
// Call Uint64 concurrently with no Seed.
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func() {
defer wg.Done()
ch <- Uint64()
}()
}
case 2:
// Start with Uint64 to pick the fast source, then call
// Seed and Uint64 concurrently.
ch <- Uint64()
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func(s int64) {
defer wg.Done()
Seed(s)
}(int64(i) + 100)
}
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func() {
defer wg.Done()
ch <- Uint64()
}()
}
default:
t.Fatalf("internal error: unrecognized code %d", code)
}

go func() {
wg.Wait()
close(ch)
}()

m := make(map[uint64]bool)
for i := range ch {
if m[i] {
t.Errorf("saw %d twice", i)
}
m[i] = true
}
}
Loading

0 comments on commit 0b9974d

Please sign in to comment.