Skip to content

Commit bd644b9

Browse files
Update random picker to use math/rand's Intn function
math/rand's Intn function is 4x faster faster and more portable. fixes #871
1 parent 0422e81 commit bd644b9

File tree

2 files changed

+33
-10
lines changed

2 files changed

+33
-10
lines changed

route/picker.go

+6-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package route
22

33
import (
4+
"math/rand"
45
"sync/atomic"
5-
"time"
66
)
77

88
// picker selects a target from a list of targets.
@@ -27,17 +27,13 @@ func rrPicker(r *Route) *Target {
2727
return u
2828
}
2929

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

route/picker_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net/url"
55
"reflect"
66
"testing"
7+
"time"
78
)
89

910
var (
@@ -56,3 +57,29 @@ func TestRRPicker(t *testing.T) {
5657
}
5758
}
5859
}
60+
61+
// This is an improved version of the previous UnixNano implementation
62+
// This one does not overflow on 32 bit platforms, it casts to int after
63+
// doing mod. doing it before caused overflows.
64+
var oldRandInt = func(n int) int {
65+
if n == 0 {
66+
return 0
67+
}
68+
return int(time.Now().UnixNano()/int64(time.Microsecond) % int64(n))
69+
}
70+
71+
var result int // prevent compiler optimization
72+
func BenchmarkOldRandIntn(b *testing.B) {
73+
var r int // more shields against compiler optimization
74+
for i := 0; i < b.N; i++ {
75+
r = oldRandInt(i)
76+
}
77+
result = r
78+
}
79+
func BenchmarkMathRandIntn(b *testing.B) {
80+
var r int // more shields against compiler optimization
81+
for i := 0; i < b.N; i++ {
82+
r = randIntn(i)
83+
}
84+
result = r
85+
}

0 commit comments

Comments
 (0)