Last active
August 29, 2015 14:12
-
-
Save zach-klippenstein/93b0d9f0499b1597754f to your computer and use it in GitHub Desktop.
Comparative benchmarks of various methods of providing RNG sources to concurrent goroutines.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package source_bench_test | |
import ( | |
"github.com/stretchr/testify/require" | |
"math/rand" | |
"sync" | |
"testing" | |
) | |
// See https://golang.org/src/math/rand/rand.go | |
type LockedSource struct { | |
lk sync.Mutex | |
src rand.Source | |
} | |
func NewLockedSource(seed int64) rand.Source { | |
return &LockedSource{src: rand.NewSource(seed)} | |
} | |
func (src *LockedSource) Int63() (n int64) { | |
src.lk.Lock() | |
n = src.src.Int63() | |
src.lk.Unlock() | |
return | |
} | |
func (src *LockedSource) Seed(seed int64) { | |
src.lk.Lock() | |
src.src.Seed(seed) | |
src.lk.Unlock() | |
} | |
type ChanBufferedSource chan int64 | |
func NewChanBufferedSource(size int, seed int64) rand.Source { | |
source := rand.NewSource(seed) | |
sourceChan := make(chan int64, size) | |
go func() { | |
for { | |
sourceChan <- source.Int63() | |
} | |
}() | |
return ChanBufferedSource(sourceChan) | |
} | |
func (src ChanBufferedSource) Int63() int64 { | |
return <-src | |
} | |
func (src ChanBufferedSource) Seed(seed int64) { | |
panic("ChanBufferedSource doesn't support seeding") | |
} | |
type BufferedSource struct { | |
size int | |
src rand.Source | |
buffer []int64 | |
lk sync.Mutex | |
} | |
func NewBufferedSource(size int, seed int64) rand.Source { | |
return &BufferedSource{ | |
size: size, | |
src: rand.NewSource(1), | |
} | |
} | |
func (src *BufferedSource) Int63() int64 { | |
src.lk.Lock() | |
defer src.lk.Unlock() | |
if len(src.buffer) == 0 { | |
src.buffer = make([]int64, src.size) | |
for i := 0; i < src.size; i++ { | |
src.buffer[i] = src.src.Int63() | |
} | |
} | |
val := src.buffer[0] | |
src.buffer = src.buffer[1:] | |
return val | |
} | |
func (src *BufferedSource) Seed(seed int64) { | |
panic("BufferedSource does not support seed") | |
} | |
// See http://vigna.di.unimi.it/ftp/papers/xorshift.pdf | |
type XorShift64Source struct { | |
state uint64 | |
} | |
func (src *XorShift64Source) Seed(seed int64) { | |
src.state = uint64(seed) | |
} | |
func (src *XorShift64Source) Int63() int64 { | |
x := src.state | |
x ^= x >> 12 // a | |
x ^= x << 25 // b | |
x ^= x >> 27 // c | |
src.state = x | |
return int64((x * 2685821657736338717) >> 1) | |
} | |
func TestXorShift64(t *testing.T) { | |
source := XorShift64Source{1} | |
for i := 0; i < 999; i++ { | |
val := source.Int63() | |
require.True(t, val >= 0, "Source returned %d < 0", val) | |
} | |
} | |
func BenchmarkCreateUnlocked(b *testing.B) { | |
b.RunParallel(func(pb *testing.PB) { | |
for pb.Next() { | |
rand.NewSource(1) | |
} | |
}) | |
} | |
func BenchmarkReadFromUnlocked(b *testing.B) { | |
source := rand.NewSource(1) | |
b.ResetTimer() | |
for i := 0; i < b.N; i++ { | |
source.Int63() | |
} | |
} | |
func BenchmarkReadFromXorShift64(b *testing.B) { | |
source := &XorShift64Source{1} | |
b.ResetTimer() | |
b.RunParallel(func(pb *testing.PB) { | |
for pb.Next() { | |
source.Int63() | |
} | |
}) | |
} | |
func BenchmarkReadFromLocked(b *testing.B) { | |
source := NewLockedSource(1) | |
b.ResetTimer() | |
b.RunParallel(func(pb *testing.PB) { | |
for pb.Next() { | |
source.Int63() | |
} | |
}) | |
} | |
func BenchmarkPool(b *testing.B) { | |
pool := sync.Pool{ | |
New: func() interface{} { | |
return rand.NewSource(1) | |
}, | |
} | |
b.ResetTimer() | |
b.RunParallel(func(pb *testing.PB) { | |
for pb.Next() { | |
source := pool.Get().(rand.Source) | |
pool.Put(source) | |
} | |
}) | |
} | |
func BenchmarkChanBuffered1(b *testing.B) { | |
source := NewChanBufferedSource(1, 1) | |
b.ResetTimer() | |
b.RunParallel(func(pb *testing.PB) { | |
for pb.Next() { | |
source.Int63() | |
} | |
}) | |
} | |
func BenchmarkChanBuffered100(b *testing.B) { | |
source := NewChanBufferedSource(100, 1) | |
b.ResetTimer() | |
b.RunParallel(func(pb *testing.PB) { | |
for pb.Next() { | |
source.Int63() | |
} | |
}) | |
} | |
func BenchmarkBuffered1(b *testing.B) { | |
source := NewBufferedSource(1, 1) | |
b.ResetTimer() | |
b.RunParallel(func(pb *testing.PB) { | |
for pb.Next() { | |
source.Int63() | |
} | |
}) | |
} | |
func BenchmarkBuffered100(b *testing.B) { | |
source := NewBufferedSource(100, 1) | |
b.ResetTimer() | |
b.RunParallel(func(pb *testing.PB) { | |
for pb.Next() { | |
source.Int63() | |
} | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment