pgregory.net/rand@v1.0.3-0.20230808192358-a0b8ce02f4da/README.md (about) 1 # rand [![PkgGoDev][godev-img]][godev] [![CI][ci-img]][ci] 2 3 Fast, high quality alternative to `math/rand` and `golang.org/x/exp/rand`. 4 5 Compared to these packages, `pgregory.net/rand`: 6 7 - is API-compatible with all `*rand.Rand` methods and all top-level functions except `Seed()`, 8 - is significantly faster, while improving the generator quality, 9 - has simpler generator initialization: 10 - `rand.New()` instead of `rand.New(rand.NewSource(time.Now().UnixNano()))` 11 - `rand.New(1)` instead of `rand.New(rand.NewSource(1))` 12 - is deliberately not providing top-level `Seed()` and the `Source` interface. 13 14 ## Benchmarks 15 16 All benchmarks were run on `Intel(R) Xeon(R) CPU E5-2680 v3 @ 2.50GHz`, 17 `linux/amd64`. 18 19 Compared to [math/rand](https://pkg.go.dev/math/rand): 20 21 ``` 22 name old time/op new time/op delta 23 Float64-48 180ns ± 8% 1ns ±12% -99.66% (p=0.000 n=10+9) 24 Intn-48 186ns ± 2% 1ns ±11% -99.63% (p=0.000 n=10+10) 25 Intn_Big-48 200ns ± 1% 1ns ±19% -99.28% (p=0.000 n=8+10) 26 Uint64-48 193ns ± 5% 1ns ± 3% -99.72% (p=0.000 n=9+9) 27 Rand_New 12.7µs ± 4% 0.1µs ± 6% -99.38% (p=0.000 n=10+10) 28 Rand_ExpFloat64 9.66ns ± 3% 5.90ns ± 3% -38.97% (p=0.000 n=10+10) 29 Rand_Float32 8.90ns ± 5% 2.04ns ± 4% -77.05% (p=0.000 n=10+10) 30 Rand_Float64 7.62ns ± 4% 2.93ns ± 3% -61.50% (p=0.000 n=10+10) 31 Rand_Int 7.74ns ± 3% 2.92ns ± 2% -62.30% (p=0.000 n=10+10) 32 Rand_Int31 7.81ns ± 3% 1.92ns ±14% -75.39% (p=0.000 n=10+10) 33 Rand_Int31n 12.7ns ± 3% 3.0ns ± 3% -76.66% (p=0.000 n=9+10) 34 Rand_Int31n_Big 12.6ns ± 4% 3.4ns ±10% -73.39% (p=0.000 n=10+10) 35 Rand_Int63 7.78ns ± 4% 2.96ns ± 6% -61.97% (p=0.000 n=10+9) 36 Rand_Int63n 26.4ns ± 1% 3.6ns ± 3% -86.56% (p=0.000 n=10+10) 37 Rand_Int63n_Big 26.2ns ± 7% 6.2ns ± 4% -76.53% (p=0.000 n=10+10) 38 Rand_Intn 14.4ns ± 5% 3.5ns ± 3% -75.72% (p=0.000 n=10+10) 39 Rand_Intn_Big 28.8ns ± 2% 6.0ns ± 6% -79.03% (p=0.000 n=10+10) 40 Rand_NormFloat64 10.7ns ± 6% 6.0ns ± 3% -44.28% (p=0.000 n=10+10) 41 Rand_Perm 1.34µs ± 1% 0.39µs ± 3% -70.86% (p=0.000 n=9+10) 42 Rand_Read 289ns ± 5% 104ns ± 5% -64.00% (p=0.000 n=10+10) 43 Rand_Seed 10.9µs ± 4% 0.0µs ± 6% -99.69% (p=0.000 n=10+10) 44 Rand_Shuffle 790ns ± 4% 374ns ± 5% -52.63% (p=0.000 n=10+10) 45 Rand_ShuffleOverhead 522ns ± 3% 204ns ± 4% -60.83% (p=0.000 n=10+10) 46 Rand_Uint32 7.82ns ± 3% 1.72ns ± 3% -77.98% (p=0.000 n=10+9) 47 Rand_Uint64 9.59ns ± 3% 2.83ns ± 2% -70.47% (p=0.000 n=10+9) 48 49 name old alloc/op new alloc/op delta 50 Rand_New 5.42kB ± 0% 0.05kB ± 0% -99.12% (p=0.000 n=10+10) 51 Rand_Perm 416B ± 0% 416B ± 0% ~ (all equal) 52 53 name old allocs/op new allocs/op delta 54 Rand_New 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.000 n=10+10) 55 Rand_Perm 1.00 ± 0% 1.00 ± 0% ~ (all equal) 56 57 name old speed new speed delta 58 Rand_Read 887MB/s ± 4% 2464MB/s ± 4% +177.83% (p=0.000 n=10+10) 59 Rand_Uint32 511MB/s ± 3% 2306MB/s ± 7% +350.86% (p=0.000 n=10+10) 60 Rand_Uint64 834MB/s ± 3% 2811MB/s ± 4% +236.85% (p=0.000 n=10+10) 61 ``` 62 63 <details> 64 <summary>Compared to <a href="https://pkg.go.dev/golang.org/x/exp/rand">golang.org/x/exp/rand</a>:</summary> 65 66 ``` 67 name old time/op new time/op delta 68 Float64-48 175ns ± 8% 1ns ±12% -99.65% (p=0.000 n=10+9) 69 Intn-48 176ns ±10% 1ns ±11% -99.61% (p=0.000 n=10+10) 70 Intn_Big-48 174ns ± 1% 1ns ±19% -99.18% (p=0.000 n=9+10) 71 Uint64-48 157ns ± 5% 1ns ± 3% -99.66% (p=0.000 n=10+9) 72 Rand_New 78.8ns ± 6% 78.3ns ± 6% ~ (p=0.853 n=10+10) 73 Rand_ExpFloat64 8.94ns ± 6% 5.90ns ± 3% -34.00% (p=0.000 n=10+10) 74 Rand_Float32 9.67ns ± 5% 2.04ns ± 4% -78.89% (p=0.000 n=10+10) 75 Rand_Float64 8.56ns ± 5% 2.93ns ± 3% -65.74% (p=0.000 n=10+10) 76 Rand_Int 5.75ns ± 3% 2.92ns ± 2% -49.25% (p=0.000 n=9+10) 77 Rand_Int31 5.72ns ± 5% 1.92ns ±14% -66.37% (p=0.000 n=10+10) 78 Rand_Int31n 17.4ns ± 7% 3.0ns ± 3% -82.87% (p=0.000 n=10+10) 79 Rand_Int31n_Big 17.3ns ± 4% 3.4ns ±10% -80.57% (p=0.000 n=10+10) 80 Rand_Int63 5.77ns ± 4% 2.96ns ± 6% -48.73% (p=0.000 n=10+9) 81 Rand_Int63n 17.0ns ± 2% 3.6ns ± 3% -79.13% (p=0.000 n=9+10) 82 Rand_Int63n_Big 26.5ns ± 2% 6.2ns ± 4% -76.81% (p=0.000 n=10+10) 83 Rand_Intn 17.5ns ± 5% 3.5ns ± 3% -79.94% (p=0.000 n=10+10) 84 Rand_Intn_Big 27.5ns ± 3% 6.0ns ± 6% -78.09% (p=0.000 n=10+10) 85 Rand_NormFloat64 10.0ns ± 3% 6.0ns ± 3% -40.45% (p=0.000 n=10+10) 86 Rand_Perm 1.31µs ± 1% 0.39µs ± 3% -70.04% (p=0.000 n=10+10) 87 Rand_Read 334ns ± 1% 104ns ± 5% -68.88% (p=0.000 n=8+10) 88 Rand_Seed 5.36ns ± 2% 33.73ns ± 6% +528.91% (p=0.000 n=10+10) 89 Rand_Shuffle 1.22µs ± 2% 0.37µs ± 5% -69.36% (p=0.000 n=10+10) 90 Rand_ShuffleOverhead 907ns ± 2% 204ns ± 4% -77.45% (p=0.000 n=10+10) 91 Rand_Uint32 5.20ns ± 5% 1.72ns ± 3% -66.84% (p=0.000 n=10+9) 92 Rand_Uint64 5.14ns ± 5% 2.83ns ± 2% -44.85% (p=0.000 n=10+9) 93 Rand_Uint64n 17.6ns ± 3% 3.5ns ± 2% -80.32% (p=0.000 n=10+10) 94 Rand_Uint64n_Big 27.3ns ± 2% 6.0ns ± 7% -77.97% (p=0.000 n=10+10) 95 Rand_MarshalBinary 30.5ns ± 1% 3.8ns ± 4% -87.70% (p=0.000 n=8+10) 96 Rand_UnmarshalBinary 3.22ns ± 4% 3.71ns ± 3% +15.16% (p=0.000 n=10+10) 97 98 name old alloc/op new alloc/op delta 99 Rand_New 48.0B ± 0% 48.0B ± 0% ~ (all equal) 100 Rand_Perm 416B ± 0% 416B ± 0% ~ (all equal) 101 Rand_MarshalBinary 16.0B ± 0% 0.0B -100.00% (p=0.000 n=10+10) 102 Rand_UnmarshalBinary 0.00B 0.00B ~ (all equal) 103 104 name old allocs/op new allocs/op delta 105 Rand_New 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.000 n=10+10) 106 Rand_Perm 1.00 ± 0% 1.00 ± 0% ~ (all equal) 107 Rand_MarshalBinary 1.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10) 108 Rand_UnmarshalBinary 0.00 0.00 ~ (all equal) 109 110 name old speed new speed delta 111 Rand_Read 764MB/s ± 3% 2464MB/s ± 4% +222.68% (p=0.000 n=9+10) 112 Rand_Uint32 770MB/s ± 5% 2306MB/s ± 7% +199.35% (p=0.000 n=10+10) 113 Rand_Uint64 1.56GB/s ± 5% 2.81GB/s ± 4% +80.32% (p=0.000 n=10+10) 114 ``` 115 </details> 116 117 <details> 118 <summary>Compared to <a href="https://pkg.go.dev/github.com/valyala/fastrand">github.com/valyala/fastrand</a>:</summary> 119 120 Note that `fastrand` [does not](https://gist.github.com/flyingmutant/bf3bd489ee3c7a32f40714c11325d614) 121 generate good random numbers. 122 123 ``` 124 name old time/op new time/op delta 125 Uint64-48 3.20ns ±35% 0.53ns ± 3% -83.29% (p=0.000 n=10+9) 126 Intn-48 1.83ns ±21% 0.69ns ±11% -62.35% (p=0.000 n=10+10) 127 ``` 128 </details> 129 130 ## FAQ 131 132 ### Why did you write this? 133 134 `math/rand` is both slow and [not up to standards]( 135 https://gist.github.com/flyingmutant/ad5841f5e594aa8687fe47de34985e6a) 136 in terms of quality (but can not be changed because of Go 1 compatibility promise). 137 `golang.org/x/exp/rand` fixes some (but [not all]( 138 https://gist.github.com/flyingmutant/0b380f432308beaaf09c0a038f918aa4)) 139 quality issues, without improving the speed, 140 and it seems that there is no active development happening there. 141 142 ### How does this thing work? 143 144 This package builds on 4 primitives: raw 64-bit generation using `sfc64` or goroutine-local 145 `runtime.fastrand64()`, floating-point generation using floating-point multiplication, 146 and integer generation in range using 32.64 or 64.128 fixed-point multiplication. 147 148 ### Why is it fast? 149 150 The primitives selected are (as far as I am aware) about as fast as you can go 151 without sacrificing quality. On top of that, it is mainly making sure the compiler 152 is able to inline code, and a couple of micro-optimizations. 153 154 ### Why no `Source`? 155 156 In Go (but not in C++ or Rust) it is a costly abstraction that provides no real value. 157 How often do you use a non-default `Source` with `math/rand`? 158 159 ### Why no top-level `Seed()`? 160 161 Top-level `Seed()` would require sharing global mutex-protected state between all top-level 162 functions, which (unlike the goroutine-local state used) does not scale when the parallelism increases. 163 164 ### Why `sfc64`? 165 166 I like it. It has withstood the test of time, with no known flaws or weaknesses despite 167 a lot of effort and CPU-hours spent on finding them. Also, it provides guarantees about period 168 length and distance between generators seeded with different seeds. And it is fast. 169 170 ### Why not... 171 172 #### ...`pcg`? 173 174 A bit slow. Otherwise, [`pcg64dxsm`](https://numpy.org/devdocs/reference/random/bit_generators/pcg64dxsm.html) 175 is probably a fine choice. 176 177 #### ...`xoshiro`/`xoroshiro`? 178 179 Quite a bit of controversy and people finding weaknesses in variants of this design. 180 Did you know that `xoshiro256**`, which author describes as an "all-purpose, rock-solid generator" 181 that "passes all tests we are aware of", fails them in seconds if you multiply the output by 57? 182 183 #### ...`splitmix`? 184 185 With 64-bit state and 64-bit output, `splitmix64` outputs every 64-bit number exactly once 186 over its 2^64 period — in other words, the probability of generating the same number is 0. 187 A [birthday test](https://www.pcg-random.org/posts/birthday-test.html) will quickly find 188 this problem. 189 190 #### ...`wyrand`? 191 192 An excellent generator if you are OK with slightly lower quality. Because its output function 193 (unlike `splitmix`) is not a bijection, some outputs are more likely to appear than others. 194 You can easily observe this non-uniformity with 195 a [birthday test](https://gist.github.com/flyingmutant/cb69e96872023f9f580868e746d1128a). 196 On most modern platforms, top-level functions in this package use goroutine-local `wyrand` 197 generators provided by the runtime as the best compromise between quality and speed 198 of concurrent execution. 199 200 #### ...`romu`? 201 202 Very fast, but relatively new and untested. Also, no guarantees about the period length. 203 204 ## Status 205 206 `pgregory.net/rand` is stable. In addition to API stability, deterministic pseudo-random 207 generation produces the same results on 32-bit and 64-bit architectures, both little-endian 208 and big-endian. Any observable change to these results would only occur together 209 with a major version bump. 210 211 ## License 212 213 `pgregory.net/rand` is licensed under the [Mozilla Public License Version 2.0](./LICENSE). 214 215 [godev-img]: https://pkg.go.dev/badge/pgregory.net/rand 216 [godev]: https://pkg.go.dev/pgregory.net/rand 217 [ci-img]: https://github.com/flyingmutant/rand/workflows/CI/badge.svg 218 [ci]: https://github.com/flyingmutant/rand/actions