Random Thoughts

Recent content on Random Thoughts

马上订阅 Random Thoughts RSS 更新: https://blog.joway.io/index.xml

Golang rand 库锁竞争优化

2020年12月17日 08:00

背景

最近在实现一个随机负载均衡器的时候发现一个问题,在高并发的情况下,官方标准库 rand.Intn() 性能会急剧下降。翻了下实现以后才发现它内部居然是全局共享了同一个 globalRand 对象。

一段测试代码:

func BenchmarkGlobalRand(b *testing.B) {
 b.RunParallel(func(pb *testing.PB) {
 for pb.Next() {
 rand.Intn(100)
 }
 })
}

func BenchmarkCustomRand(b *testing.B) {
 b.RunParallel(func(pb *testing.PB) {
 rd := rand.New(rand.NewSource(time.Now().Unix()))
 for pb.Next() {
 rd.Intn(100)
 }
 })
}

输出:

BenchmarkGlobalRand
BenchmarkGlobalRand-8 18075486 66.1 ns/op
BenchmarkCustomRand
BenchmarkCustomRand-8 423686118 2.38 ns/op

解决思路

最理想对情况是可以在每个 goroutine 内创建一个私有的 rand.Rand 对象,从而实现真正的无锁。

但在很多其他场景下,我们并不能直接控制调用我们的 goroutine,又或者 goroutine 数量过多以至于无法承受这部分内存成本。

此时的一个思路是使用 sync.Pool 来为 rand.Source 创建一个池,当多线程并发读写时,优先从自己当前 P 中的 poolLocal 中获取,从而减少锁的竞争。同时由于只是用 pool 扩展了原生的 rngSource 对象,所以可以兼容其 rand.Rand 下的所有接口调用。

基于这个思路,实现了一个 fastrand 库放到了 github。

从 benchmark 中可以看到性能提升显著,在并发条件下,比原生全局 rand 快了大约 8 倍.

BenchmarkStandardRand
BenchmarkStandardRand-8 60870416 19.1 ns/op 0 B/op 0 allocs/op
BenchmarkFastRand
BenchmarkFastRand-8 100000000 10.7 ns/op 0 B/op 0 allocs/op
BenchmarkStandardRandWithConcurrent
BenchmarkStandardRandWithConcurrent-8 18058663 67.8 ns/op 0 B/op 0 allocs/op
BenchmarkFastRandWithConcurrent
BenchmarkFastRandWithConcurrent-8 132542940 8.79 ns/op 0 B/op 0 allocs/op