Skip to content

Commit

Permalink
Update random picker to use math/rand's Intn function
Browse files Browse the repository at this point in the history
math/rand's Intn function is 4x faster faster and more portable.

fixes #871
  • Loading branch information
nathanejohnson committed Jul 16, 2022
1 parent 0422e81 commit bd644b9
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 10 deletions.
16 changes: 6 additions & 10 deletions route/picker.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package route

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

// picker selects a target from a list of targets.
Expand All @@ -27,17 +27,13 @@ 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 randIntn = func(n int) int {
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
}

0 comments on commit bd644b9

Please sign in to comment.