├── bench.sh ├── go.mod ├── .gitignore ├── .github └── workflows │ └── go.yml ├── LICENSE ├── go.sum ├── benchRNG_test.go ├── README.md ├── fastrand64.go └── fastrand64_test.go /bench.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | go test -v -benchmem -bench=.* -benchtime 2s 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/villenny/fastrand64-go 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/stretchr/testify v1.5.1 7 | github.com/tsuna/endian v0.0.0-20151020052604-29b3a4178852 8 | github.com/valyala/fastrand v1.0.0 9 | github.com/yalue/native_endian v0.0.0-20180607135909-51013b03be4f 10 | ) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | coverage.* 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.13 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | 33 | - name: Build 34 | run: go build -v . 35 | 36 | - name: Test 37 | run: go test -v -coverprofile=coverage.txt -covermode=atomic . 38 | 39 | - name: Upload coverage to Codecov 40 | uses: codecov/codecov-action@v1 41 | with: 42 | file: ./coverage.txt 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ryan Haksi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 8 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 9 | github.com/tsuna/endian v0.0.0-20151020052604-29b3a4178852 h1:/HMzghBx/U8ZTQ+CCKRAsjeNNV12OCG3PfJcthNMBU0= 10 | github.com/tsuna/endian v0.0.0-20151020052604-29b3a4178852/go.mod h1:7SvkOZYNBtjd5XUi2fuPMvAZS8rlCMaU69hj/3joIsE= 11 | github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI= 12 | github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= 13 | github.com/yalue/native_endian v0.0.0-20180607135909-51013b03be4f h1:nsQCScpQ8RRf+wIooqfyyEUINV2cAPuo2uVtHSBbA4M= 14 | github.com/yalue/native_endian v0.0.0-20180607135909-51013b03be4f/go.mod h1:1cm5YQZdnDQBZVtFG2Ip8sFVN0eYZ8OFkCT2kIVl9mw= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 16 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 17 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 18 | -------------------------------------------------------------------------------- /benchRNG_test.go: -------------------------------------------------------------------------------- 1 | package fastrand64 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | "unsafe" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | type UnsafePcg32RNG struct { 13 | state uint64 14 | inc uint64 15 | } 16 | 17 | func (r *UnsafePcg32RNG) SetState(initstate uint64, initseq uint64) { 18 | r.state = 0 19 | r.inc = (initseq << 1) | 1 20 | r.Uint32() 21 | r.state += initstate 22 | r.Uint32() 23 | } 24 | 25 | func (r *UnsafePcg32RNG) Seed(seed int64) { 26 | r.SetState( 27 | Splitmix64(uint64(seed)+uint64(0)), 28 | Splitmix64(uint64(seed)+uint64(1)), 29 | ) 30 | } 31 | 32 | func (r *UnsafePcg32RNG) Uint32() uint32 { 33 | oldstate := r.state 34 | r.state = oldstate*6364136223846793005 + r.inc 35 | xorshifted := ((oldstate >> 18) ^ oldstate) >> 27 36 | rot := oldstate >> 59 37 | result := (xorshifted >> rot) | (xorshifted << ((-rot) & 31)) 38 | return uint32(result) 39 | } 40 | 41 | func NewUnsafePcg32RNG(seed int64) *UnsafePcg32RNG { 42 | r := &UnsafePcg32RNG{} 43 | r.Seed(seed) 44 | return r 45 | } 46 | 47 | type UnsafePcg32x2RNG struct { 48 | gen0 UnsafePcg32RNG 49 | gen1 UnsafePcg32RNG 50 | } 51 | 52 | func (r *UnsafePcg32x2RNG) SetState(seed1 uint64, seq1 uint64, seed2 uint64, seq2 uint64) { 53 | mask := ^uint64(0) >> 1 54 | // The stream for each of the two generators *must* be distinct 55 | if (seq1 & mask) == (seq2 & mask) { 56 | seq2 = ^seq2 57 | } 58 | r.gen0.SetState(seed1, seq1) 59 | r.gen1.SetState(seed2, seq2) 60 | } 61 | 62 | func (r *UnsafePcg32x2RNG) Seed(seed int64) { 63 | r.SetState( 64 | Splitmix64(uint64(seed)+uint64(0)), 65 | Splitmix64(uint64(seed)+uint64(1)), 66 | Splitmix64(uint64(seed)+uint64(2)), 67 | Splitmix64(uint64(seed)+uint64(3)), 68 | ) 69 | } 70 | 71 | func (r *UnsafePcg32x2RNG) Uint64() uint64 { 72 | return (uint64(r.gen0.Uint32()) << 32) | uint64(r.gen1.Uint32()) 73 | } 74 | 75 | func NewUnsafePcg32x2RNG(seed int64) *UnsafePcg32x2RNG { 76 | r := &UnsafePcg32x2RNG{} 77 | r.Seed(seed) 78 | return r 79 | } 80 | 81 | type UnsafeJsf64RNG struct { 82 | a uint64 83 | b uint64 84 | c uint64 85 | d uint64 86 | } 87 | 88 | func rot64(x uint64, k uint64) uint64 { 89 | return ((x << k) | (x >> (64 - k))) 90 | } 91 | 92 | func (x *UnsafeJsf64RNG) Uint64() uint64 { 93 | e := x.a - rot64(x.b, 7) 94 | x.a = x.b ^ rot64(x.c, 13) 95 | x.b = x.c + rot64(x.d, 37) 96 | x.c = x.d + e 97 | x.d = e + x.a 98 | return x.d 99 | } 100 | 101 | func (x *UnsafeJsf64RNG) Seed(seed int64) { 102 | x.a = 0xf1ea5eed 103 | x.b = uint64(seed) 104 | x.c = uint64(seed) 105 | x.d = uint64(seed) 106 | for i := 0; i < 20; i++ { 107 | x.Uint64() 108 | } 109 | } 110 | 111 | func NewUnsafeJsf64RNG(seed int64) *UnsafeJsf64RNG { 112 | r := &UnsafeJsf64RNG{} 113 | r.Seed(seed) 114 | return r 115 | } 116 | 117 | func Test_UnsafeCast(t *testing.T) { 118 | var i uint64 = 1 119 | b := make([]byte, 16) 120 | 121 | header := (*reflect.SliceHeader)(unsafe.Pointer(&b)) 122 | ptr := (*uint64)(unsafe.Pointer(header.Data)) 123 | 124 | ptr = (*uint64)(unsafe.Pointer((uintptr)(unsafe.Pointer(ptr)) + 8)) 125 | *ptr = i 126 | 127 | assert.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, b) 128 | } 129 | 130 | // //////////////////////////////////////////////////////////////// 131 | 132 | /* 133 | 134 | Running tool: C:\Go\bin\go.exe test -benchmem -run=^$ github.com/villenny/concurrency-go -bench ^(Benchmark_UnsafePcg32x2RNG|Benchmark_UnsafeJsf64RNG)$ 135 | 136 | goos: windows 137 | goarch: amd64 138 | pkg: github.com/villenny/concurrency-go 139 | Benchmark_UnsafePcg32x2RNG-8 223279117 5.28 ns/op 0 B/op 0 allocs/op 140 | Benchmark_UnsafeJsf64RNG-8 295147552 4.10 ns/op 0 B/op 0 allocs/op 141 | PASS 142 | ok github.com/villenny/concurrency-go 3.550s 143 | 144 | */ 145 | func Benchmark_UnsafePcg32x2RNG(b *testing.B) { 146 | rng := NewUnsafePcg32x2RNG(time.Now().UnixNano()) 147 | var r uint64 148 | for i := 0; i < b.N; i++ { 149 | r = rng.Uint64() 150 | } 151 | BenchSink = &r 152 | } 153 | 154 | func Benchmark_UnsafeJsf64RNG(b *testing.B) { 155 | rng := NewUnsafeJsf64RNG(time.Now().UnixNano()) 156 | var r uint64 157 | for i := 0; i < b.N; i++ { 158 | r = rng.Uint64() 159 | } 160 | BenchSink = &r 161 | } 162 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub issues](https://img.shields.io/github/issues/Villenny/fastrand64-go)](https://github.com/Villenny/fastrand64-go/issues) 2 | [![GitHub forks](https://img.shields.io/github/forks/Villenny/fastrand64-go)](https://github.com/Villenny/fastrand64-go/network) 3 | [![GitHub stars](https://img.shields.io/github/stars/Villenny/fastrand64-go)](https://github.com/Villenny/fastrand64-go/stargazers) 4 | [![GitHub license](https://img.shields.io/github/license/Villenny/fastrand64-go)](https://github.com/Villenny/fastrand64-go/blob/master/LICENSE) 5 | ![Go](https://github.com/Villenny/fastrand64-go/workflows/Go/badge.svg?branch=master) 6 | ![Codecov branch](https://img.shields.io/codecov/c/github/villenny/fastrand64-go/master) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/Villenny/fastrand64-go)](https://goreportcard.com/report/github.com/Villenny/fastrand64-go) 8 | [![Documentation](https://godoc.org/github.com/Villenny/fastrand64-go?status.svg)](http://godoc.org/github.com/Villenny/fastrand64-go) 9 | 10 | # fastrand64-go 11 | Helper library for full uint64 randomness, pool backed for efficient concurrency 12 | 13 | Inspired by https://github.com/valyala/fastrand which is 20% faster, but not as good a source of randomness. 14 | 15 | 16 | ## Install 17 | 18 | ``` 19 | go get -u github.com/Villenny/fastrand64-go 20 | ``` 21 | 22 | ## Notable members: 23 | `Xoshiro256ssRNG`, 24 | `SyncPoolRNG`, 25 | 26 | The expected use case: 27 | - If you are doing a lot of random indexing on a lot of cores 28 | ``` 29 | import "github.com/villenny/fastrand64-go" 30 | 31 | rng := NewSyncPoolXoshiro256ssRNG() 32 | 33 | // somewhere later, in some goproc, one of lots, like a web request handler for example 34 | 35 | r1 := rng.Uint32n(10) 36 | r2 := rng.Uint64() 37 | someBytes := rng.Bytes(256) 38 | ``` 39 | 40 | Using SyncPoolRNG: 41 | - I tried to keep everything safe for composition, this way you can use your own random generator if you have one 42 | - Note the pool uses the the builtin golang threadsafe uint64 rand function to generate seeds for each allocated generator in the pool. 43 | ``` 44 | import "github.com/villenny/concurrency-go" 45 | 46 | // use the helper function to generate a system rand based source 47 | rand.Seed(1) 48 | rng := NewSyncPoolRNG(func() UnsafeRNG { return NewUnsafeRandRNG(int64(rand.Uint64())) }) 49 | 50 | // use some random thing that has a Uint64() function 51 | rand.Seed(1) 52 | rng := NewSyncPoolRNG(func() UnsafeRNG { return rand.New(rand.NewSource(rand.Uint64()).(rand.Source64)) }) 53 | 54 | ``` 55 | 56 | 57 | ## Benchmark 58 | 59 | - Xoshiro256ss is roughly 3X faster than whatever golang uses natively 60 | - The Pool wrapped version of Xoshiro is roughly half as fast as the native threadsafe golang random generator, almost entirely due to the cost of checking into and out of the pool. But I benchmarked on my own machine, and linux might have a faster sync.Pool. 61 | - BUT, the pool wrapped Xoshiro generator murders the native in a multicore environment where there would otherwise be lots of contention. 4X faster on my 4 core machine in the pathological case of every core doing nothing but generate random numbers. 62 | - It would probably be faster still (although I havent tested this, unsafe xoshiro256** is 10X faster than the pooled xoshiro256**) to feed each goproc its own unsafe generator in their context arg and not use the pool. 63 | 64 | 65 | ``` 66 | goos: windows 67 | goarch: amd64 68 | pkg: github.com/villenny/concurrency-go 69 | Benchmark_UnsafeXoshiro256ssRNG 70 | Benchmark_UnsafeXoshiro256ssRNG-8 886575583 2.65 ns/op 0 B/op 0 allocs/op 71 | Benchmark_UnsafeRandRNG 72 | Benchmark_UnsafeRandRNG-8 331377972 7.34 ns/op 0 B/op 0 allocs/op 73 | Benchmark_FastModulo 74 | Benchmark_FastModulo-8 321618081 7.49 ns/op 0 B/op 0 allocs/op 75 | Benchmark_Modulo 76 | Benchmark_Modulo-8 290857617 8.18 ns/op 0 B/op 0 allocs/op 77 | Benchmark_SyncPoolXoshiro256ssRNG_Uint32n_Serial 78 | Benchmark_SyncPoolXoshiro256ssRNG_Uint32n_Serial-8 66759944 35.4 ns/op 0 B/op 0 allocs/op 79 | Benchmark_SyncPoolXoshiro256ssRNG_Uint32n_Parallel 80 | Benchmark_SyncPoolXoshiro256ssRNG_Uint32n_Parallel-8 283647373 8.68 ns/op 0 B/op 0 allocs/op 81 | Benchmark_SyncPoolXoshiro256ssRNG_Uint64_Serial 82 | Benchmark_SyncPoolXoshiro256ssRNG_Uint64_Serial-8 68642816 35.0 ns/op 0 B/op 0 allocs/op 83 | Benchmark_SyncPoolUnsafeRandRNG_Uint64_Serial 84 | Benchmark_SyncPoolUnsafeRandRNG_Uint64_Serial-8 64929618 37.9 ns/op 0 B/op 0 allocs/op 85 | Benchmark_SyncPoolXoshiro256ssRNG_Uint64_Parallel 86 | Benchmark_SyncPoolXoshiro256ssRNG_Uint64_Parallel-8 276466162 8.75 ns/op 0 B/op 0 allocs/op 87 | Benchmark_SyncPoolUnsafeRandRNG_Uint64_Parallel 88 | Benchmark_SyncPoolUnsafeRandRNG_Uint64_Parallel-8 263430684 8.87 ns/op 0 B/op 0 allocs/op 89 | Benchmark_Rand_Int31n_Serial 90 | Benchmark_Rand_Int31n_Serial-8 140250397 17.3 ns/op 0 B/op 0 allocs/op 91 | Benchmark_Rand_Int31n_Parallel 92 | Benchmark_Rand_Int31n_Parallel-8 24767929 94.8 ns/op 0 B/op 0 allocs/op 93 | Benchmark_ValyalaFastrand_Int31n_Serial 94 | Benchmark_ValyalaFastrand_Int31n_Serial-8 100000000 24.5 ns/op 0 B/op 0 allocs/op 95 | Benchmark_ValyalaFastrand_Int31n_Parallel 96 | Benchmark_ValyalaFastrand_Int31n_Parallel-8 340295736 8.60 ns/op 0 B/op 0 allocs/op 97 | Benchmark_Rand_Uint64_Serial 98 | Benchmark_Rand_Uint64_Serial-8 160272944 14.8 ns/op 0 B/op 0 allocs/op 99 | Benchmark_Rand_Uint64_Parallel 100 | Benchmark_Rand_Uint64_Parallel-8 26694428 88.7 ns/op 0 B/op 0 allocs/op 101 | Benchmark_SyncPoolBytes_Serial_64bytes 102 | Benchmark_SyncPoolBytes_Serial_64bytes-8 2222467 1069 ns/op 288 B/op 4 allocs/op 103 | Benchmark_SyncPoolBytes_Serial_1024bytes 104 | Benchmark_SyncPoolBytes_Serial_1024bytes-8 1000000 2248 ns/op 1248 B/op 4 allocs/op 105 | Benchmark_SyncPoolBytes_Parallel_1024bytes 106 | Benchmark_SyncPoolBytes_Parallel_1024bytes-8 4044595 543 ns/op 1024 B/op 1 allocs/op 107 | PASS 108 | ok github.com/villenny/concurrency-go 57.292s 109 | 110 | 111 | ``` 112 | 113 | ## Contact 114 | 115 | Ryan Haksi [ryan.haksi@gmail.com] 116 | 117 | ## License 118 | 119 | Available under the MIT [License](/LICENSE). 120 | -------------------------------------------------------------------------------- /fastrand64.go: -------------------------------------------------------------------------------- 1 | // Package fastrand64 implements fast pesudorandom number generator 2 | // that should scale well on multi-CPU systems. 3 | // 4 | // Use crypto/rand instead of this package for generating 5 | // cryptographically secure random numbers. 6 | // 7 | // Example: 8 | // 9 | // import "github.com/villenny/fastrand64-go" 10 | // 11 | // make a threadsafe random generator 12 | // rng := NewSyncPoolXoshiro256ssRNG() 13 | // 14 | // // somewhere later, in some goproc, one of lots, like a web request handler for example 15 | // // this (ab)uses a sync.Pool to allocate one generator per thread 16 | // r1 := rng.Uint32n(10) 17 | // r2 := rng.Uint64() 18 | // someBytes := rng.Bytes(8) 19 | // 20 | // // This will produce R1=, R2=, someBytes= 21 | // fmt.Printf("R1=%v, R2=%v, someBytes=%v", r1, r2, someBytes) 22 | package fastrand64 23 | 24 | import ( 25 | "math/rand" 26 | "sync" 27 | "time" 28 | ) 29 | 30 | // ThreadsafePoolRNG core type for the pool backed threadsafe RNG 31 | type ThreadsafePoolRNG struct { 32 | rngPool sync.Pool 33 | } 34 | 35 | // UnsafeRNG is the interface for an unsafe RNG used by the Pool RNG as a source of randomness 36 | type UnsafeRNG interface { 37 | Uint64() uint64 38 | } 39 | 40 | // NewSyncPoolRNG Wraps a sync.Pool around a thread unsafe RNG, thus making it efficiently thread safe 41 | func NewSyncPoolRNG(fn func() UnsafeRNG) *ThreadsafePoolRNG { 42 | s := &ThreadsafePoolRNG{} 43 | s.rngPool = sync.Pool{New: func() interface{} { return fn() }} 44 | return s 45 | } 46 | 47 | // NewSyncPoolXoshiro256ssRNG conveniently allocations a thread safe pooled back xoshiro256** generator 48 | // this uses NewSyncPoolRNG internally 49 | func NewSyncPoolXoshiro256ssRNG() *ThreadsafePoolRNG { 50 | rand.Seed(time.Now().UnixNano()) 51 | return NewSyncPoolRNG(func() UnsafeRNG { 52 | return NewUnsafeXoshiro256ssRNG(int64(rand.Uint64())) 53 | }) 54 | } 55 | 56 | // Uint64 returns pseudorandom uint64. Threadsafe 57 | func (s *ThreadsafePoolRNG) Uint64() uint64 { 58 | r := s.rngPool.Get().(UnsafeRNG) 59 | x := r.Uint64() 60 | s.rngPool.Put(r) 61 | return x 62 | } 63 | 64 | // Int63 is here to match Source64 interface, why not call Int64 65 | func (s *ThreadsafePoolRNG) Int63() int64 { 66 | return int64(0x7FFFFFFFFFFFFFFF & s.Uint64()) 67 | } 68 | 69 | // Seed is only here to match the golang std libs Source64 interface 70 | func (s *ThreadsafePoolRNG) Seed(seed int64) { 71 | // you cant really seed a PoolRNG, since the call order is non-determinate 72 | panic("Cant seed a ThreadsafePoolRNG") 73 | } 74 | 75 | // Bytes allocates a []byte filled with random bytes and returns it. This is convenient 76 | // but caller does the allocation pattern is better way since it can reduce allocation count/GC 77 | func (s *ThreadsafePoolRNG) Bytes(n int) []byte { 78 | r := s.rngPool.Get().(UnsafeRNG) 79 | bytes := make([]byte, n) 80 | result := Bytes(r, bytes) 81 | s.rngPool.Put(r) 82 | return result 83 | } 84 | 85 | // Read fills a []byte array with random bytes from a thread safe pool backed RNG 86 | func (s *ThreadsafePoolRNG) Read(p []byte) []byte { 87 | r := s.rngPool.Get().(UnsafeRNG) 88 | Bytes(r, p) 89 | s.rngPool.Put(r) 90 | return p 91 | } 92 | 93 | // Bytes fills a []byte array with random bytes from a thread unsafe RNG 94 | func Bytes(r UnsafeRNG, bytes []byte) []byte { 95 | n := len(bytes) 96 | 97 | /* 98 | header := (*reflect.SliceHeader)(unsafe.Pointer(&bytes)) 99 | ptr := (*uint64)(unsafe.Pointer(header.Data)) 100 | offsetMax := uintptr(n - (n % 8)) 101 | ptrMax := (*uint64)(unsafe.Pointer(header.Data + offsetMax)) 102 | 103 | for { 104 | if ptr == ptrMax { 105 | break 106 | } 107 | x := r.Uint64() 108 | *ptr = x 109 | ptr = (*uint64)(unsafe.Pointer((uintptr)(unsafe.Pointer(ptr)) + 8)) 110 | } 111 | 112 | i := int(offsetMax) 113 | */ 114 | 115 | i := 0 116 | iMax := n - (n % 8) 117 | for { 118 | if i == iMax { 119 | break 120 | } 121 | x := r.Uint64() 122 | 123 | bytes[i] = byte(x) 124 | bytes[i+1] = byte(x >> 8) 125 | bytes[i+2] = byte(x >> 16) 126 | bytes[i+3] = byte(x >> 24) 127 | bytes[i+4] = byte(x >> 32) 128 | bytes[i+5] = byte(x >> 40) 129 | bytes[i+6] = byte(x >> 48) 130 | bytes[i+7] = byte(x >> 56) 131 | i += 8 132 | } 133 | 134 | x := r.Uint64() 135 | for { 136 | if i >= n { 137 | break 138 | } 139 | bytes[i] = byte(x) 140 | x >>= 8 141 | i += 1 142 | } 143 | 144 | return bytes 145 | } 146 | 147 | // Uint32n returns pseudorandom Uint32n in the range [0..maxN). 148 | // 149 | // It is safe calling this function from concurrent goroutines. 150 | func (s *ThreadsafePoolRNG) Uint32n(maxN int) uint32 { 151 | x := s.Uint64() & 0x00000000FFFFFFFF 152 | // See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ 153 | return uint32((x * uint64(maxN)) >> 32) 154 | } 155 | 156 | // UnsafeXoshiro256ssRNG It is unsafe to call UnsafeRNG methods from concurrent goroutines. 157 | // 158 | // UnsafeXoshiro256** is a pseudorandom number generator. 159 | // For an interesting commentary on xoshiro256** 160 | // https://www.pcg-random.org/posts/a-quick-look-at-xoshiro256.html 161 | // 162 | // It is however very fast, and strong enough for most practical purposes 163 | type UnsafeXoshiro256ssRNG struct { 164 | s0 uint64 165 | s1 uint64 166 | s2 uint64 167 | s3 uint64 168 | } 169 | 170 | func rol64(x uint64, k uint64) uint64 { 171 | return (x << k) | (x >> (64 - k)) 172 | } 173 | 174 | // Splitmix64 is typically used to convert a potentially zero seed, into better non-zero seeds 175 | // ie seeding a stronger RNG 176 | func Splitmix64(index uint64) uint64 { 177 | z := (index + uint64(0x9E3779B97F4A7C15)) 178 | z = (z ^ (z >> 30)) * uint64(0xBF58476D1CE4E5B9) 179 | z = (z ^ (z >> 27)) * uint64(0x94D049BB133111EB) 180 | z = z ^ (z >> 31) 181 | return z 182 | } 183 | 184 | // Uint64 generates a random Uin64, (not thread safe) 185 | func (r *UnsafeXoshiro256ssRNG) Uint64() uint64 { 186 | // See https://en.wikipedia.org/wiki/Xorshift 187 | result := rol64(r.s1*5, 7) * 9 188 | t := r.s1 << 17 189 | 190 | r.s2 ^= r.s0 191 | r.s3 ^= r.s1 192 | r.s1 ^= r.s2 193 | r.s0 ^= r.s3 194 | 195 | r.s2 ^= t 196 | r.s3 = rol64(r.s3, 45) 197 | 198 | return result 199 | } 200 | 201 | // Seed takes a single uint64 and runs it through splitmix64 to seed the 256 bit starting state for the RNG 202 | func (r *UnsafeXoshiro256ssRNG) Seed(seed int64) { 203 | i := 0 204 | for r.s0 = 0; r.s0 == 0; i++ { 205 | r.s0 = Splitmix64(uint64(seed) + uint64(i)) 206 | } 207 | for r.s1 = 0; r.s1 == 0; i++ { 208 | r.s1 = Splitmix64(uint64(seed) + uint64(i)) 209 | } 210 | for r.s2 = 0; r.s2 == 0; i++ { 211 | r.s2 = Splitmix64(uint64(seed) + uint64(i)) 212 | } 213 | for r.s3 = 0; r.s3 == 0; i++ { 214 | r.s3 = Splitmix64(uint64(seed) + uint64(i)) 215 | } 216 | } 217 | 218 | // NewUnsafeXoshiro256ssRNG creates a new Thread unsafe PRNG generator 219 | func NewUnsafeXoshiro256ssRNG(seed int64) *UnsafeXoshiro256ssRNG { 220 | r := &UnsafeXoshiro256ssRNG{} 221 | r.Seed(seed) 222 | return r 223 | } 224 | 225 | // NewUnsafeRandRNG creates a new Thread unsafe PRNG generator using the native golang 64bit RNG generator 226 | // (thus avoiding using any global state) 227 | func NewUnsafeRandRNG(seed int64) *rand.Rand { 228 | return rand.New(rand.NewSource(seed).(rand.Source64)) 229 | } 230 | -------------------------------------------------------------------------------- /fastrand64_test.go: -------------------------------------------------------------------------------- 1 | package fastrand64 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "math/rand" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/tsuna/endian" 12 | "github.com/valyala/fastrand" 13 | "github.com/yalue/native_endian" 14 | ) 15 | 16 | func Test_SafeRNG_Bytes(t *testing.T) { 17 | rng := NewSyncPoolXoshiro256ssRNG() 18 | bytes := rng.Bytes(255) 19 | assert.Equal(t, 255, len(bytes)) 20 | } 21 | 22 | func Test_SafeRNG_UInt32n(t *testing.T) { 23 | rng := NewSyncPoolXoshiro256ssRNG() 24 | for i := 0; i < 4096; i++ { 25 | r := rng.Uint32n(10) 26 | assert.Less(t, r, uint32(10)) 27 | } 28 | } 29 | 30 | func Test_SafeRNG_UInt64(t *testing.T) { 31 | rng1 := NewSyncPoolRNG(func() UnsafeRNG { return NewUnsafeRandRNG(1) }) 32 | rng2 := NewUnsafeRandRNG(1) 33 | for i := 0; i < 256; i++ { 34 | r1 := rng1.Uint64() 35 | r2 := rng2.Uint64() 36 | assert.Equal(t, r1, r2) 37 | } 38 | } 39 | 40 | func Test_SafeRNG_Seed(t *testing.T) { 41 | rng := NewSyncPoolXoshiro256ssRNG() 42 | assert.Panics(t, func() { rng.Seed(0) }) 43 | } 44 | 45 | func Test_SafeRNG_Int63(t *testing.T) { 46 | rng1 := NewSyncPoolRNG(func() UnsafeRNG { return NewUnsafeRandRNG(1) }) 47 | rng2 := NewUnsafeRandRNG(1) 48 | for i := 0; i < 256; i++ { 49 | r1 := rng1.Int63() 50 | r2 := int64(0x7FFFFFFFFFFFFFFF & rng2.Uint64()) 51 | assert.Equal(t, r1, r2) 52 | } 53 | } 54 | 55 | func Test_SafeRNG_Read(t *testing.T) { 56 | rng1 := NewSyncPoolRNG(func() UnsafeRNG { return NewUnsafeRandRNG(1) }) 57 | rng2 := NewUnsafeRandRNG(1) 58 | 59 | b := make([]byte, 8) 60 | rng1.Read(b) 61 | var r1 uint64 62 | buf := bytes.NewBuffer(b) 63 | _ = binary.Read(buf, native_endian.NativeEndian(), &r1) 64 | 65 | r2 := rng2.Uint64() 66 | 67 | assert.Equal(t, r1, r2) 68 | } 69 | 70 | func Test_UnsafeXoshiro256ssRNG_UInt64(t *testing.T) { 71 | rng := UnsafeXoshiro256ssRNG{s0: 0x01d353e5f3993bb0, s1: 0x7b9c0df6cb193b20, s2: 0xfdfcaa91110765b6, s3: 0xd2db341f10bb232e} 72 | var r uint64 73 | 74 | /* this should produce: 75 | 0000 dd 51 b2 b7 d9 30 3a 37 eb d9 63 66 a6 70 fd 50 |.Q...0:7..cf.p.P| 76 | 0010 26 e7 29 1f 21 21 c0 35 36 c1 2d 03 77 b1 41 d3 |&.).!!.56.-.w.A.| 77 | 0020 43 33 2f 77 f7 fe 97 01 1e 93 c3 ce e4 df fc c4 |C3/w............| 78 | 0030 db 6c 06 54 08 25 6f 5a 0e 86 82 4d 1c 72 c9 50 |.l.T.%oZ...M.r.P| 79 | 0040 20 ae ca 84 d9 24 87 b9 51 96 93 ae ae d2 8f ce | ....$..Q.......| 80 | 0050 57 37 c1 5c f4 cc 5c d6 2a 29 72 cb f0 c5 f8 f8 |W7.\..\.*)r.....| 81 | 0060 46 1e 33 a2 5d b1 66 b4 15 6f 3b ed 93 e4 70 ba |F.3.].f..o;...p.| 82 | 0070 11 be 24 b0 20 64 13 86 71 72 92 31 d8 be 03 a9 |..$. d..qr.1....| 83 | 0080 78 6f 73 68 69 72 6f 32 35 36 2a 2a 20 62 79 20 |xoshiro256** by | 84 | 0090 42 6c 61 63 6b 6d 61 6e 20 26 20 56 69 67 6e 61 |Blackman & Vigna| 85 | 00a0 bd 9a f9 bd 3a 79 52 d3 76 50 5e 1e 55 6a 36 48 |....:yR.vP^.Uj6H| 86 | 00b0 9f c0 39 c2 5c db 99 a3 5c d5 4b a2 15 35 53 9c |..9.\...\.K..5S.| 87 | 00c0 da dd c6 0b bf 33 ef a7 82 eb 06 52 6d 6d 31 2b |.....3.....Rmm1+| 88 | 00d0 24 7a 0c 3f 70 43 d1 6f aa c6 88 7e f9 30 ee ff |$z.?pC.o...~.0..| 89 | 00e0 22 31 af c6 1f e5 68 22 e9 6e 30 06 f6 7f 9a 6e |"1....h".n0....n| 90 | 00f0 be 19 0c f7 ae e2 fa ec 8e c6 22 e1 78 b6 39 d1 |..........".x.9.| 91 | */ 92 | 93 | r = rng.Uint64() 94 | assert.Equal(t, r, endian.HostToNetUint64(uint64(0xdd51b2b7d9303a37))) 95 | r = rng.Uint64() 96 | assert.Equal(t, r, endian.HostToNetUint64(uint64(0xebd96366a670fd50))) 97 | } 98 | 99 | func Test_NewUnsafeRandRNG_UInt64(t *testing.T) { 100 | rng := NewUnsafeRandRNG(1) 101 | r := rng.Uint64() 102 | assert.Equal(t, rand.New(rand.NewSource(1).(rand.Source64)).Uint64(), r) 103 | } 104 | 105 | // /////////////////////////////////////////////////////////////////////////// 106 | // 107 | // B E N C H M A R K S 108 | 109 | var BenchSink interface{} 110 | 111 | func Benchmark_UnsafeXoshiro256ssRNG(b *testing.B) { 112 | rng := NewUnsafeXoshiro256ssRNG(time.Now().UnixNano()) 113 | var r uint64 114 | for i := 0; i < b.N; i++ { 115 | r = rng.Uint64() 116 | } 117 | BenchSink = &r 118 | } 119 | 120 | func Benchmark_UnsafeRandRNG(b *testing.B) { 121 | rng := NewUnsafeRandRNG(time.Now().UnixNano()) 122 | var r uint64 123 | for i := 0; i < b.N; i++ { 124 | r = rng.Uint64() 125 | } 126 | BenchSink = &r 127 | } 128 | 129 | func Benchmark_UnsafePcg32RNG(b *testing.B) { 130 | rng := NewUnsafePcg32RNG(time.Now().UnixNano()) 131 | var r uint32 132 | for i := 0; i < b.N; i++ { 133 | r = rng.Uint32() 134 | } 135 | BenchSink = &r 136 | } 137 | 138 | func Benchmark_FastModulo(b *testing.B) { 139 | maxN := uint64(10) 140 | rng := NewUnsafeRandRNG(time.Now().UnixNano()) 141 | var r uint32 142 | for i := 0; i < b.N; i++ { 143 | x := rng.Uint64() & 0x00000000FFFFFFFF 144 | r = uint32((x * (maxN)) >> 32) 145 | } 146 | BenchSink = &r 147 | } 148 | 149 | func Benchmark_Modulo(b *testing.B) { 150 | maxN := uint64(10) 151 | rng := NewUnsafeRandRNG(time.Now().UnixNano()) 152 | var r uint32 153 | for i := 0; i < b.N; i++ { 154 | r = uint32(rng.Uint64() % (maxN)) 155 | } 156 | BenchSink = &r 157 | } 158 | 159 | func Benchmark_SyncPoolXoshiro256ssRNG_Uint32n_Serial(b *testing.B) { 160 | rng := NewSyncPoolXoshiro256ssRNG() 161 | var r uint32 162 | for i := 0; i < b.N; i++ { 163 | r = rng.Uint32n(10) 164 | } 165 | BenchSink = &r 166 | } 167 | 168 | func Benchmark_SyncPoolXoshiro256ssRNG_Uint32n_Parallel(b *testing.B) { 169 | rng := NewSyncPoolXoshiro256ssRNG() 170 | b.RunParallel(func(pb *testing.PB) { 171 | r := rng.Uint32n(10) 172 | for pb.Next() { 173 | r = rng.Uint32n(10) 174 | } 175 | BenchSink = &r 176 | }) 177 | } 178 | 179 | func Benchmark_SyncPoolXoshiro256ssRNG_Uint64_Serial(b *testing.B) { 180 | rng := NewSyncPoolXoshiro256ssRNG() 181 | var r uint64 182 | for i := 0; i < b.N; i++ { 183 | r = rng.Uint64() 184 | } 185 | BenchSink = &r 186 | } 187 | 188 | func Benchmark_SyncPoolUnsafeRandRNG_Uint64_Serial(b *testing.B) { 189 | rand.Seed(1) 190 | rng := NewSyncPoolRNG(func() UnsafeRNG { return NewUnsafeRandRNG(int64(rand.Uint64())) }) 191 | var r uint64 192 | for i := 0; i < b.N; i++ { 193 | r = rng.Uint64() 194 | } 195 | BenchSink = &r 196 | } 197 | 198 | func Benchmark_SyncPoolXoshiro256ssRNG_Uint64_Parallel(b *testing.B) { 199 | rng := NewSyncPoolXoshiro256ssRNG() 200 | b.RunParallel(func(pb *testing.PB) { 201 | r := rng.Uint64() 202 | for pb.Next() { 203 | r = rng.Uint64() 204 | } 205 | BenchSink = &r 206 | }) 207 | } 208 | 209 | func Benchmark_SyncPoolUnsafeRandRNG_Uint64_Parallel(b *testing.B) { 210 | rand.Seed(1) 211 | rng := NewSyncPoolRNG(func() UnsafeRNG { return NewUnsafeRandRNG(int64(rand.Uint64())) }) 212 | b.RunParallel(func(pb *testing.PB) { 213 | r := rng.Uint64() 214 | for pb.Next() { 215 | r = rng.Uint64() 216 | } 217 | BenchSink = &r 218 | }) 219 | } 220 | 221 | func Benchmark_Rand_Int31n_Serial(b *testing.B) { 222 | var r int32 223 | for i := 0; i < b.N; i++ { 224 | r = rand.Int31n(10) 225 | } 226 | BenchSink = &r 227 | } 228 | 229 | func Benchmark_Rand_Int31n_Parallel(b *testing.B) { 230 | b.RunParallel(func(pb *testing.PB) { 231 | r := rand.Int31n(10) 232 | for pb.Next() { 233 | r = rand.Int31n(10) 234 | } 235 | BenchSink = &r 236 | }) 237 | } 238 | func Benchmark_ValyalaFastrand_Int31n_Serial(b *testing.B) { 239 | r := fastrand.Uint32n(10) 240 | for i := 0; i < b.N; i++ { 241 | r = fastrand.Uint32n(10) 242 | } 243 | BenchSink = &r 244 | } 245 | 246 | func Benchmark_ValyalaFastrand_Int31n_Parallel(b *testing.B) { 247 | b.RunParallel(func(pb *testing.PB) { 248 | r := fastrand.Uint32n(10) 249 | for pb.Next() { 250 | r = fastrand.Uint32n(10) 251 | } 252 | BenchSink = &r 253 | }) 254 | } 255 | func Benchmark_Rand_Uint64_Serial(b *testing.B) { 256 | var r uint64 257 | for i := 0; i < b.N; i++ { 258 | r = rand.Uint64() 259 | } 260 | BenchSink = &r 261 | } 262 | 263 | func Benchmark_Rand_Uint64_Parallel(b *testing.B) { 264 | b.RunParallel(func(pb *testing.PB) { 265 | r := rand.Uint64() 266 | for pb.Next() { 267 | r = rand.Uint64() 268 | } 269 | BenchSink = &r 270 | }) 271 | } 272 | 273 | func Benchmark_SyncPoolBytes_Serial_64bytes(b *testing.B) { 274 | rng := NewSyncPoolXoshiro256ssRNG() 275 | var bytes []byte 276 | for i := 0; i < b.N; i++ { 277 | bytes = rng.Bytes(64) 278 | } 279 | assert.Equal(b, 64, len(bytes)) 280 | } 281 | 282 | func Benchmark_SyncPoolBytes_Serial_1024bytes(b *testing.B) { 283 | rng := NewSyncPoolXoshiro256ssRNG() 284 | var bytes []byte 285 | for i := 0; i < b.N; i++ { 286 | bytes = rng.Bytes(1024) 287 | } 288 | assert.Equal(b, 1024, len(bytes)) 289 | } 290 | 291 | func Benchmark_SyncPoolBytes_Serial_1Mbytes(b *testing.B) { 292 | rng := NewSyncPoolXoshiro256ssRNG() 293 | var bytes []byte 294 | for i := 0; i < b.N; i++ { 295 | bytes = rng.Bytes(1024 * 1024) 296 | } 297 | assert.Equal(b, 1024*1024, len(bytes)) 298 | } 299 | 300 | func Benchmark_SyncPoolBytes_Parallel_1024bytes(b *testing.B) { 301 | rng := NewSyncPoolXoshiro256ssRNG() 302 | b.RunParallel(func(pb *testing.PB) { 303 | bytes := rng.Bytes(1024) 304 | for pb.Next() { 305 | bytes = rng.Bytes(1024) 306 | } 307 | BenchSink = &bytes 308 | }) 309 | } 310 | 311 | /* 312 | goos: windows 313 | goarch: amd64 314 | pkg: github.com/villenny/fastrand64-go 315 | Benchmark_UnsafeXoshiro256ssRNG-8 455017189 2.68 ns/op 0 B/op 0 allocs/op 316 | Benchmark_UnsafeRandRNG-8 157644103 7.52 ns/op 0 B/op 0 allocs/op 317 | Benchmark_UnsafePcg32RNG-8 553564772 2.24 ns/op 0 B/op 0 allocs/op 318 | Benchmark_FastModulo-8 162549952 7.43 ns/op 0 B/op 0 allocs/op 319 | Benchmark_Modulo-8 150343704 7.79 ns/op 0 B/op 0 allocs/op 320 | Benchmark_SyncPoolXoshiro256ssRNG_Uint32n_Serial-8 35329238 34.5 ns/op 0 B/op 0 allocs/op 321 | Benchmark_SyncPoolXoshiro256ssRNG_Uint32n_Parallel-8 147113551 8.37 ns/op 0 B/op 0 allocs/op 322 | Benchmark_SyncPoolXoshiro256ssRNG_Uint64_Serial-8 34326316 33.8 ns/op 0 B/op 0 allocs/op 323 | Benchmark_SyncPoolUnsafeRandRNG_Uint64_Serial-8 32465599 37.3 ns/op 0 B/op 0 allocs/op 324 | Benchmark_SyncPoolXoshiro256ssRNG_Uint64_Parallel-8 146641885 8.36 ns/op 0 B/op 0 allocs/op 325 | Benchmark_SyncPoolUnsafeRandRNG_Uint64_Parallel-8 126181107 8.91 ns/op 0 B/op 0 allocs/op 326 | Benchmark_Rand_Int31n_Serial-8 70664302 17.3 ns/op 0 B/op 0 allocs/op 327 | Benchmark_Rand_Int31n_Parallel-8 12513151 94.4 ns/op 0 B/op 0 allocs/op 328 | Benchmark_ValyalaFastrand_Int31n_Serial-8 50053598 24.1 ns/op 0 B/op 0 allocs/op 329 | Benchmark_ValyalaFastrand_Int31n_Parallel-8 158120858 7.97 ns/op 0 B/op 0 allocs/op 330 | Benchmark_Rand_Uint64_Serial-8 85800085 14.8 ns/op 0 B/op 0 allocs/op 331 | Benchmark_Rand_Uint64_Parallel-8 12012588 87.1 ns/op 0 B/op 0 allocs/op 332 | Benchmark_SyncPoolBytes_Serial_64bytes-8 8964448 144 ns/op 64 B/op 1 allocs/op 333 | Benchmark_SyncPoolBytes_Serial_1024bytes-8 1000000 1311 ns/op 1024 B/op 1 allocs/op 334 | Benchmark_SyncPoolBytes_Serial_1Mbytes-8 945 1625825 ns/op 1048923 B/op 1 allocs/op 335 | Benchmark_SyncPoolBytes_Parallel_1024bytes-8 2053411 577 ns/op 1024 B/op 1 allocs/op 336 | PASS 337 | ok github.com/villenny/fastrand64-go 32.704s 338 | */ 339 | --------------------------------------------------------------------------------