Skip to content
This repository was archived by the owner on Aug 17, 2020. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions rand/locked_rand.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package rand

import (
"math/rand"
"sync"
)

// LockedRand implements NumberGenerator and embeds Rand that is safe for concurrent use.
type LockedRand struct {
lk sync.Mutex
r *rand.Rand
}

// NewLockedRand creates a new LockedRand that implements all Rand functions that is safe
// for concurrent use.
func NewLockedRand(seed int64) *LockedRand {
return &LockedRand{
r: rand.New(rand.NewSource(seed)),
}
}

// Seed uses the provided seed value to initialize the generator to a deterministic state.
// Seed should not be called concurrently with any other Rand method.
func (lr *LockedRand) Seed(seed int64) {
lr.lk.Lock()
lr.r.Seed(seed)
lr.lk.Unlock()
}

// TwoInt63 generates 2 random int64 without locking twice.
func (lr *LockedRand) TwoInt63() (n1, n2 int64) {
lr.lk.Lock()
n1 = lr.r.Int63()
n2 = lr.r.Int63()
lr.lk.Unlock()
return
}

// Int63 returns a non-negative pseudo-random 63-bit integer as an int64.
func (lr *LockedRand) Int63() (n int64) {
lr.lk.Lock()
n = lr.r.Int63()
lr.lk.Unlock()
return
}

// Uint32 returns a pseudo-random 32-bit value as a uint32.
func (lr *LockedRand) Uint32() (n uint32) {
lr.lk.Lock()
n = lr.r.Uint32()
lr.lk.Unlock()
return
}

// Uint64 returns a pseudo-random 64-bit value as a uint64.
func (lr *LockedRand) Uint64() (n uint64) {
lr.lk.Lock()
n = lr.r.Uint64()
lr.lk.Unlock()
return
}

// Int31 returns a non-negative pseudo-random 31-bit integer as an int32.
func (lr *LockedRand) Int31() (n int32) {
lr.lk.Lock()
n = lr.r.Int31()
lr.lk.Unlock()
return
}

// Int returns a non-negative pseudo-random int.
func (lr *LockedRand) Int() (n int) {
lr.lk.Lock()
n = lr.r.Int()
lr.lk.Unlock()
return
}

// Int63n returns, as an int64, a non-negative pseudo-random number in [0,n).
// It panics if n <= 0.
func (lr *LockedRand) Int63n(n int64) (r int64) {
lr.lk.Lock()
r = lr.r.Int63n(n)
lr.lk.Unlock()
return
}

// Int31n returns, as an int32, a non-negative pseudo-random number in [0,n).
// It panics if n <= 0.
func (lr *LockedRand) Int31n(n int32) (r int32) {
lr.lk.Lock()
r = lr.r.Int31n(n)
lr.lk.Unlock()
return
}

// Intn returns, as an int, a non-negative pseudo-random number in [0,n).
// It panics if n <= 0.
func (lr *LockedRand) Intn(n int) (r int) {
lr.lk.Lock()
r = lr.r.Intn(n)
lr.lk.Unlock()
return
}

// Float64 returns, as a float64, a pseudo-random number in [0.0,1.0).
func (lr *LockedRand) Float64() (n float64) {
lr.lk.Lock()
n = lr.r.Float64()
lr.lk.Unlock()
return
}

// Float32 returns, as a float32, a pseudo-random number in [0.0,1.0).
func (lr *LockedRand) Float32() (n float32) {
lr.lk.Lock()
n = lr.r.Float32()
lr.lk.Unlock()
return
}

// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n).
func (lr *LockedRand) Perm(n int) (r []int) {
lr.lk.Lock()
r = lr.r.Perm(n)
lr.lk.Unlock()
return
}

// Read generates len(p) random bytes and writes them into p. It
// always returns len(p) and a nil error.
// Read should not be called concurrently with any other Rand method.
func (lr *LockedRand) Read(p []byte) (n int, err error) {
lr.lk.Lock()
n, err = lr.r.Read(p)
lr.lk.Unlock()
return
}
7 changes: 7 additions & 0 deletions rand/num_gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package rand

// NumberGenerator defines an interface to generate numbers.
type NumberGenerator interface {
Int63() int64
TwoInt63() (int64, int64)
}
59 changes: 59 additions & 0 deletions rand/pool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package rand

import (
"math/rand"
"sync/atomic"
)

// Pool represents a pool of random number generators.
// To generate a random id, round robin through the source pool with atomic increment.
// With more and more goroutines, Pool improves the performance of Random vs naive global random
// mutex exponentially.
// Try tests with 20000 goroutines and 500 calls to observe the difference
type Pool struct {
sources []NumberGenerator
counter uint64 // used for round robin
size uint64
}

// see bit twiddling hacks: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
func nextNearestPow2uint64(v uint64) uint64 {
v--
v |= v >> 1
v |= v >> 2
v |= v >> 4
v |= v >> 8
v |= v >> 16
v |= v >> 32
v++
return v
}

// NewPool takes in a size and creates a pool of random id generators with size equal to next closest power of 2.
// eg: NewPool(10) returns a pool with 2^4 = 16 random sources.
func NewPool(seed int64, size uint64) *Pool {
groupsize := nextNearestPow2uint64(size)
pool := &Pool{
size: groupsize,
sources: make([]NumberGenerator, groupsize),
}
// seed the pool
pool.seed(seed)
return pool
}

// seed initializes the pool using a randomized sequence with given seed.
func (r *Pool) seed(seed int64) {
// init a random sequence to seed all sources
seedRan := rand.NewSource(seed)
for i := uint64(0); i < r.size; i++ {
r.sources[i] = NewLockedRand(seedRan.Int63())
}
}

// Pick returns a NumberGenerator from a pool of NumberGenerators
func (r *Pool) Pick() NumberGenerator {
// use round robin with fast modulus of pow2 numbers
selection := atomic.AddUint64(&r.counter, 1) & (r.size - 1)
return r.sources[selection]
}
26 changes: 15 additions & 11 deletions tracer/util.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
package tracer

import (
"math/rand"
"sync"
"runtime"
"time"

"go.undefinedlabs.com/scopeagent/rand"
)

var (
seededIDGen = rand.New(rand.NewSource(time.Now().UnixNano()))
// The golang rand generators are *not* intrinsically thread-safe.
seededIDLock sync.Mutex
randompool = rand.NewPool(time.Now().UnixNano(), uint64(max(16, runtime.NumCPU())))
)

// max returns the larger value among a and b
func max(x, y int) int {
if x > y {
return x
}
return y
}

func randomID() uint64 {
seededIDLock.Lock()
defer seededIDLock.Unlock()
return uint64(seededIDGen.Int63())
return uint64(randompool.Pick().Int63())
}

func randomID2() (uint64, uint64) {
seededIDLock.Lock()
defer seededIDLock.Unlock()
return uint64(seededIDGen.Int63()), uint64(seededIDGen.Int63())
n1, n2 := randompool.Pick().TwoInt63()
return uint64(n1), uint64(n2)
}