113 lines
4.4 KiB
Markdown
113 lines
4.4 KiB
Markdown
|
# Alternative Go PRNGs
|
||
|
|
||
|
Package `rng` provides several more efficient, higher quality PRNGs
|
||
|
sources for use with `math/rand.Rand`. Each PRNG implements the
|
||
|
`math/rand.Source64` interface.
|
||
|
|
||
|
What makes these PRNGs more efficient? They have tiny states — 32 bytes
|
||
|
for the largest — compared to gc's default source, which has a ~5kB
|
||
|
state. Two of the generators run faster, too. See the benchmarks below.
|
||
|
|
||
|
What generators are included?
|
||
|
|
||
|
* [SplitMix64][sm64]
|
||
|
* [32-bit and 64-bit permuted congruential generator (PCG)][pcg32]
|
||
|
* Custom 64-bit PCG using [xorshift-multiply][pr] permutation (Pcg64x)
|
||
|
* [xoshiro256\*\*][xo]
|
||
|
* A ["minimal standard" 128-bit linear congruential generator (LCG)][lcg128]
|
||
|
* A 64-bit [Middle Square Weyl Sequence][msws]
|
||
|
* RomuDuo and RomuDuoJr of the [Romu family][romu]
|
||
|
|
||
|
SplitMix64 is the fastest generator. Pcg64x is the fastest robust
|
||
|
generator.
|
||
|
|
||
|
[lcg128]: http://www.pcg-random.org/posts/does-it-beat-the-minimal-standard.html
|
||
|
[msws]: https://pthree.org/2018/07/30/middle-square-weyl-sequence-prng/
|
||
|
[pcg32]: http://www.pcg-random.org/download.html
|
||
|
[pr]: https://nullprogram.com/blog/2018/07/31/
|
||
|
[romu]: https://romu-random.org/
|
||
|
[sm64]: http://xoshiro.di.unimi.it/splitmix64.c
|
||
|
[xo]: http://xoshiro.di.unimi.it/xoshiro256starstar.c
|
||
|
|
||
|
## Example
|
||
|
|
||
|
```go
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"math/rand"
|
||
|
|
||
|
"nullprogram.com/x/rng"
|
||
|
)
|
||
|
|
||
|
func main() {
|
||
|
s := new(rng.Lcg128)
|
||
|
s.Seed(1)
|
||
|
r := rand.New(s)
|
||
|
fmt.Printf("%v\n", r.NormFloat64())
|
||
|
fmt.Printf("%v\n", r.NormFloat64())
|
||
|
fmt.Printf("%v\n", r.NormFloat64())
|
||
|
// Output:
|
||
|
// -0.5402515557248266
|
||
|
// 0.00984877400071782
|
||
|
// -0.40951475107890106
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Benchmark
|
||
|
|
||
|
The gc implementation of Go doesn't go a great job optimizing these
|
||
|
routines compared to either GCC or Clang, so SplitMix64 performs the
|
||
|
best of the algorithms in this package. The "baseline" is the default
|
||
|
source from `math/rand`, and the "interface" benchmarks call through the
|
||
|
`math/rand.Source64` interface.
|
||
|
|
||
|
$ go test -bench=.
|
||
|
goos: linux
|
||
|
goarch: amd64
|
||
|
pkg: nullprogram.com/x/rng
|
||
|
BenchmarkLcg128-8 429435103 2.72 ns/op
|
||
|
BenchmarkLcg128Interface-8 305827086 3.94 ns/op
|
||
|
BenchmarkSplitMix64-8 869916241 1.37 ns/op
|
||
|
BenchmarkSplitMix64Interface-8 349949367 3.40 ns/op
|
||
|
BenchmarkXoshiro256ss-8 345413484 3.49 ns/op
|
||
|
BenchmarkXoshiro256ssInterface-8 219676675 5.58 ns/op
|
||
|
BenchmarkPcg32-8 338895014 3.54 ns/op
|
||
|
BenchmarkPcg32Interface-8 249454053 4.90 ns/op
|
||
|
BenchmarkPcg64-8 223279998 5.40 ns/op
|
||
|
BenchmarkPcg64Interface-8 169870239 7.06 ns/op
|
||
|
BenchmarkPcg64x-8 590234182 2.08 ns/op
|
||
|
BenchmarkPcg64xInterface-8 285790071 4.18 ns/op
|
||
|
BenchmarkMsws64-8 347618887 3.43 ns/op
|
||
|
BenchmarkMsws64Interface-8 245472339 4.89 ns/op
|
||
|
BenchmarkRomuDuo-8 581715128 2.03 ns/op
|
||
|
BenchmarkRomuDuoInterface-8 331822014 3.67 ns/op
|
||
|
BenchmarkRomuDuoJr-8 643661610 1.87 ns/op
|
||
|
BenchmarkRomuDuoInterfaceJr-8 332948454 3.62 ns/op
|
||
|
BenchmarkBaseline-8 298460190 4.05 ns/op
|
||
|
|
||
|
The big takeaway here: **Interface calls are relatively expensive!** If
|
||
|
possible, use SplitMix64, and do not call it through an interface since
|
||
|
that cuts its performance in half. If you must call through an interface,
|
||
|
the built-in PRNG is the fastest, though has the worst quality and a large
|
||
|
state.
|
||
|
|
||
|
## Statistical Quality
|
||
|
|
||
|
| generator | dieharder | BigCrush | PractRand |
|
||
|
|----------------|-----------|----------|-----------|
|
||
|
| math/rand | PASS | 1 fail | 256MB |
|
||
|
| Lcg128 | PASS | 1 fail | 128GB |
|
||
|
| SplitMix64 | PASS | 1 fail | > 8TB |
|
||
|
| Xoroshiro256ss | PASS | PASS | > 8TB |
|
||
|
| Pcg32 | PASS | 1 fail | > 8TB |
|
||
|
| Pcg64 | PASS | PASS | > 8TB |
|
||
|
| Pcg64x | PASS | PASS | > 8TB |
|
||
|
| Msws64 | PASS | PASS | > 8TB |
|
||
|
| RomuDuo | PASS | PASS | > 8TB |
|
||
|
| RomuDuoJr | PASS | 3 fail | > 8TB |
|
||
|
|
||
|
Tests were run with a zero seed, dieharder 3.31.1, TestU01 1.2.3, and
|
||
|
PractRand 0.95. PractRand was stopped after 8TB of input.
|