Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update random picker to use math/rand's Intn function #892

Closed
wants to merge 3 commits into from
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
20 changes: 11 additions & 9 deletions route/picker.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package route

import (
"math/rand"
"sync"
"sync/atomic"
"time"
)
Expand All @@ -27,17 +29,17 @@ func rrPicker(r *Route) *Target {
return u
}

// stubbed out for testing
// we implement the randIntN function using the nanosecond time counter
// since it is 15x faster than using the pseudo random number generator
// (12 ns vs 190 ns) Most HW does not seem to provide clocks with ns
// resolution but seem to be good enough for µs resolution. Since
// requests are usually handled within several ms we should have enough
// variation. Within 1 ms we have 1000 µs to distribute among a smaller
// set of entities (<< 100)
// as it turns out, math/rand's Intn is now way faster (4x) than the previous implementation using
// time.UnixNano(). As a bonus, this actually works properly on 32 bit platforms.
var rndOnce sync.Once
var randIntn = func(n int) int {
rndOnce.Do(func() {
rand.Seed(time.Now().UnixNano())
})
if n == 0 {
return 0
}
return int(time.Now().UnixNano()/int64(time.Microsecond)) % n
return rand.Intn(n)
}


27 changes: 27 additions & 0 deletions route/picker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/url"
"reflect"
"testing"
"time"
)

var (
Expand Down Expand Up @@ -56,3 +57,29 @@ func TestRRPicker(t *testing.T) {
}
}
}

// This is an improved version of the previous UnixNano implementation
// This one does not overflow on 32 bit platforms, it casts to int after
// doing mod. doing it before caused overflows.
var oldRandInt = func(n int) int {
if n == 0 {
return 0
}
return int(time.Now().UnixNano()/int64(time.Microsecond) % int64(n))
}

var result int // prevent compiler optimization
func BenchmarkOldRandIntn(b *testing.B) {
var r int // more shields against compiler optimization
for i := 0; i < b.N; i++ {
r = oldRandInt(i)
}
result = r
}
func BenchmarkMathRandIntn(b *testing.B) {
var r int // more shields against compiler optimization
for i := 0; i < b.N; i++ {
r = randIntn(i)
}
result = r
}