Skip to content

Commit 0b9974d

Browse files
ianlancetaylorgopherbot
authored andcommitted
math/rand: use fastrand64 if possible
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 <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> Reviewed-by: Russ Cox <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]>
1 parent f63ee8b commit 0b9974d

File tree

3 files changed

+284
-44
lines changed

3 files changed

+284
-44
lines changed

src/math/rand/default_test.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package rand_test
6+
7+
import (
8+
"fmt"
9+
"internal/race"
10+
"internal/testenv"
11+
. "math/rand"
12+
"os"
13+
"runtime"
14+
"strconv"
15+
"sync"
16+
"testing"
17+
)
18+
19+
// Test that racy access to the default functions behaves reasonably.
20+
func TestDefaultRace(t *testing.T) {
21+
// Skip the test in short mode, but even in short mode run
22+
// the test if we are using the race detector, because part
23+
// of this is to see whether the race detector reports any problems.
24+
if testing.Short() && !race.Enabled {
25+
t.Skip("skipping starting another executable in short mode")
26+
}
27+
28+
const env = "GO_RAND_TEST_HELPER_CODE"
29+
if v := os.Getenv(env); v != "" {
30+
doDefaultTest(t, v)
31+
return
32+
}
33+
34+
t.Parallel()
35+
36+
for i := 0; i < 6; i++ {
37+
i := i
38+
t.Run(strconv.Itoa(i), func(t *testing.T) {
39+
t.Parallel()
40+
exe, err := os.Executable()
41+
if err != nil {
42+
exe = os.Args[0]
43+
}
44+
cmd := testenv.Command(t, exe, "-test.run=TestDefaultRace")
45+
cmd = testenv.CleanCmdEnv(cmd)
46+
cmd.Env = append(cmd.Env, fmt.Sprintf("GO_RAND_TEST_HELPER_CODE=%d", i/2))
47+
if i%2 != 0 {
48+
cmd.Env = append(cmd.Env, "GODEBUG=randautoseed=0")
49+
}
50+
out, err := cmd.CombinedOutput()
51+
if len(out) > 0 {
52+
t.Logf("%s", out)
53+
}
54+
if err != nil {
55+
t.Error(err)
56+
}
57+
})
58+
}
59+
}
60+
61+
// doDefaultTest should be run before there have been any calls to the
62+
// top-level math/rand functions. Make sure that we can make concurrent
63+
// calls to top-level functions and to Seed without any duplicate values.
64+
// This will also give the race detector a change to report any problems.
65+
func doDefaultTest(t *testing.T, v string) {
66+
code, err := strconv.Atoi(v)
67+
if err != nil {
68+
t.Fatalf("internal error: unrecognized code %q", v)
69+
}
70+
71+
goroutines := runtime.GOMAXPROCS(0)
72+
if goroutines < 4 {
73+
goroutines = 4
74+
}
75+
76+
ch := make(chan uint64, goroutines*3)
77+
var wg sync.WaitGroup
78+
79+
// The various tests below should not cause race detector reports
80+
// and should not produce duplicate results.
81+
//
82+
// Note: these tests can theoretically fail when using fastrand64
83+
// in that it is possible to coincidentally get the same random
84+
// number twice. That could happen something like 1 / 2**64 times,
85+
// which is rare enough that it may never happen. We don't worry
86+
// about that case.
87+
88+
switch code {
89+
case 0:
90+
// Call Seed and Uint64 concurrently.
91+
wg.Add(goroutines)
92+
for i := 0; i < goroutines; i++ {
93+
go func(s int64) {
94+
defer wg.Done()
95+
Seed(s)
96+
}(int64(i) + 100)
97+
}
98+
wg.Add(goroutines)
99+
for i := 0; i < goroutines; i++ {
100+
go func() {
101+
defer wg.Done()
102+
ch <- Uint64()
103+
}()
104+
}
105+
case 1:
106+
// Call Uint64 concurrently with no Seed.
107+
wg.Add(goroutines)
108+
for i := 0; i < goroutines; i++ {
109+
go func() {
110+
defer wg.Done()
111+
ch <- Uint64()
112+
}()
113+
}
114+
case 2:
115+
// Start with Uint64 to pick the fast source, then call
116+
// Seed and Uint64 concurrently.
117+
ch <- Uint64()
118+
wg.Add(goroutines)
119+
for i := 0; i < goroutines; i++ {
120+
go func(s int64) {
121+
defer wg.Done()
122+
Seed(s)
123+
}(int64(i) + 100)
124+
}
125+
wg.Add(goroutines)
126+
for i := 0; i < goroutines; i++ {
127+
go func() {
128+
defer wg.Done()
129+
ch <- Uint64()
130+
}()
131+
}
132+
default:
133+
t.Fatalf("internal error: unrecognized code %d", code)
134+
}
135+
136+
go func() {
137+
wg.Wait()
138+
close(ch)
139+
}()
140+
141+
m := make(map[uint64]bool)
142+
for i := range ch {
143+
if m[i] {
144+
t.Errorf("saw %d twice", i)
145+
}
146+
m[i] = true
147+
}
148+
}

0 commit comments

Comments
 (0)