持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 18 天,点击查看活动详情
尽量不用锁 RWMutex
sync.Map 适合 读多写少的情况
concurrentMap 写多读少的时候 减小锁的粒度
package lock
import (
"strconv"
"sync"
"testing"
)
const (
NumOfReader = 200
NumOfWriter = 100
)
type Map interface {
Set(key interface{}, val interface{})
Get(key interface{}) (interface{}, bool)
Del(key interface{})
}
func benchmarkMap(b *testing.B, hm Map) {
for i:=0;i<b.N;i++{
var wg sync.WaitGroup
for i:=0;i<NumOfWriter;i++{
wg.Add(1)
go func() {
for i:=0;i<100;i++{
hm.Set(strconv.Itoa(i),i*i)
hm.Set(strconv.Itoa(i),i*i)
hm.Del(strconv.Itoa(i))
}
wg.Done()
}()
}
for i:=0;i<NumOfReader;i++{
wg.Add(1)
go func() {
for i:=0;i<100;i++{
hm.Get(strconv.Itoa(i))
}
wg.Done()
}()
}
wg.Wait()
}
}
func BenchmarkSyncMap(b *testing.B) {
b.Run("map with RWLock", func(b *testing.B) {
hm:=CreateRWLockMap()
benchmarkMap(b,hm)
})
b.Run("map with SyncMap", func(b *testing.B) {
hm:=CreateSyncMapBenchMarkAdapter()
benchmarkMap(b,hm)
})
b.Run("map with ConcureentMap", func(b *testing.B) {
hm:=CreateConcureentMapBenchmarkAdapter(199)
benchmarkMap(b,hm)
})
}
NumOfReader = 100
NumOfWriter = 100
| 命名 | 同样时间执行多少次 | 一次执行了多少时间 |
|---|---|---|
| BenchmarkSyncMap/map_with_RWLock-8 | 182 | 6618349 ns/op |
| BenchmarkSyncMap/map_with_SyncMap-8 | 100 | 11801721 ns/op |
| BenchmarkSyncMap/map_with_ConcureentMap-8 | 442 | 2653011 ns/op |
NumOfReader = 100
NumOfWriter = 200
| 命名 | 同样时间执行多少次 | 一次执行了多少时间 |
|---|---|---|
| BenchmarkSyncMap/map_with_RWLock-8 | 86 | 13309623 ns/op |
| BenchmarkSyncMap/map_with_SyncMap-8 | 60 | 20316582 ns/op |
| BenchmarkSyncMap/map_with_ConcureentMap-8 | 271 | 4765438 ns/op |
NumOfReader = 200
NumOfWriter = 100
| 命名 | 同样时间执行多少次 | 一次执行了多少时间 |
|---|---|---|
| BenchmarkSyncMap/map_with_RWLock-8 | 157 | 7036303 ns/op |
| BenchmarkSyncMap/map_with_SyncMap-8 | 100 | 11625267 ns/op |
| BenchmarkSyncMap/map_with_ConcureentMap-8 | 342 | 3173536 ns/op |
其实我们可以看出,关于锁的方面。sync.Map并不是无敌,而是适合读多写少的情况。引入一个新的github.com/easierway/concurrent_map包,读多写多性能都很好,因为这个包减小了锁的粒度。
另一些性能优化的例子
-
字符串拼接使用string.Builder来优化
-
json的序列化与反序列化使用 easyjson 来优化
-
引用类型 尽量传送 引用 而不是 传值
-
切片应当在初始状态下定义好预定容量,过大占用内存空间,过小开辟内存引起消耗
-
可以内存复用,例如使用sync.Once 来Do一次,来减少性能的开销。
-
sync.Pool 使用通过复用,降低对象的创建和GC的代价,是线程安全的,但是有大锁,这也是导致性能不是很好。另外生命周期受到GC影响,不适合做连接池,需要自己管理生命周期资源的池化。所以链接池最好还是用缓冲的chan来做
-
尽量不使用反射,例如 json包中序列化与反序列化就用到了反射,fmt.Sprintf()中也用到了反射,所以两者性能并不理想。