Golang提供了非常丰富的工具来对程序进行性能分析,本文将通过基准测试来分析上篇文章中的三种并发map的效率。


1.基准测试简介

1.1 如何写基准测试?

基准测试有以下规则,

1)基准测试的代码文件必须以_test.go结尾

2)基准测试的函数必须以Benchmark开头,必须是可导出的

3)基准测试函数必须带有一个*testing.B类型的参数;它还提供了一个整数N,用于指定操作执行的循环次数。

4)基准测试函数不能有返回值


不妨和单元测试的要求放在一起比较下,

1)单元测试文件名必须以_test.go命名

2)单元测试方法必须是TestXxx开头

3)单元测试方法方法参数必须 t *testing.T


1.2 命令行参数

运行基准测试命令go test -bench=. 时可以指定以下参数,

-cpu 1,2,3 指定运行的cpu 格式

-count n 指定运行的次数

-benchtime 每一条测试执行的时间

-bench 指定执行bench的方法

-benchmem 显示内存分配情况

-v 显示详细输出


2.对map的并发实现进行基准测试

2.1 测试代码

抽象出Map接口

type Map interface {
Set(key string, val interface{})
Get(key string) (interface{}, bool)
Delete(key string)
}

封装原生map+读写锁

type RWLockMap struct {
m map[interface{}]interface{}
sync.RWMutex
}


func NewRWLockMap() *RWLockMap {
return &RWLockMap{
m: make(map[interface{}]interface{}),
}
}


func (r *RWLockMap) Get(key string) (interface{}, bool) {
r.RWMutex.RLock()
defer r.RWMutex.RUnlock()
v, ok := r.m[key]
return v, ok
}


func (r *RWLockMap) Set(k string, v interface{}) {
r.RWMutex.Lock()
defer r.RWMutex.Unlock()
r.m[k]=v
}


func (r *RWLockMap) Delete(k string){
r.RWMutex.Lock()
defer r.RWMutex.Unlock()
delete(r.m,k)
}

封装syn.Map

type SyncMap struct {
smap sync.Map
}


func NewSyncMap() *SyncMap {
return &SyncMap{}
}
func (s *SyncMap) Get(key string) (interface{}, bool) {
v, ok := s.smap.Load(key)
return v, ok
}
func (s *SyncMap) Set(k string, v interface{}) {
s.smap.Store(k, v)
}
func (s *SyncMap) Delete(k string) {
s.smap.Delete(k)
}

封装分段锁ConcurrentMap

type ConCurrentMap struct{
m cmap.ConcurrentMap
}
func NewConCurrentMap() *ConCurrentMap{
return &ConCurrentMap{m:cmap.New()}
}
func (c *ConCurrentMap)Get(k string)(interface{},bool){
v,ok:=c.m.Get(k)
return v,ok
}
func (c *ConCurrentMap)Set(k string,v interface{}){
c.m.Set(k,v)
}
func (c *ConCurrentMap)Delete(k string){
c.m.Remove(k)
}

基准测试代码

var (
Writes = 100
Reads = 100
)


func BenchmarkMaps(b *testing.B) {
b.Logf("Writes: %d,Reads: %d", Writes, Reads)


b.Run("sycMap", func(b *testing.B) {
m := NewSyncMap()
benchMarkMap(b, m)
})


b.Run("RWLockMap", func(b *testing.B) {
m := NewRWLockMap()
benchMarkMap(b, m)
})


b.Run("ConCurrentMap", func(b *testing.B) {
m := NewConCurrentMap()
benchMarkMap(b, m)
})


}


func benchMarkMap(b *testing.B, m Map) {
var wg sync.WaitGroup
for i := 0; i < b.N; i++ {


for j := 0; j < Writes; j++ {
wg.Add(1)
go func() {
for k := 0; k < 100; k++ {
m.Set(strconv.Itoa(k), k*k)
m.Set(strconv.Itoa(k), k*2)
          m.Delete(strconv.Itoa(k))
}
wg.Done()
}()
}


for j := 0; j < Reads; j++ {
wg.Add(1)
go func() {
for k := 0; k < 100; k++ {
          m.Get(strconv.Itoa(k))
}
wg.Done()
}()
    }
}
wg.Wait()
}

2.2 测试结论

运行基准测试命令

go test -v -bench=. -benchmem

其中1次Writes和Reads分别包含100次写+改+删操作,100次读操作

BenchmarkMaps
map_test.go:15: Writes: 100,Reads: 100
BenchmarkMaps/sycMap
BenchmarkMaps/sycMap-12 120 10769717 ns/op 1804918 B/op 76848 allocs/op
BenchmarkMaps/RWLockMap
BenchmarkMaps/RWLockMap-12 190 7260979 ns/op 401217 B/op 28511 allocs/op
BenchmarkMaps/ConCurrentMap
BenchmarkMaps/ConCurrentMap-12 1027 1371452 ns/op 109758 B/op 8577 allocs/op
PASS
ok _/D_/Projects/Go/Hot/Map 6.061s


测试结果显示,多读多写场景ConCurrentMap效率最高,RWLockMap次之。

BenchmarkMaps
map_test.go:15: Writes: 10,Reads: 1000
BenchmarkMaps/sycMap
BenchmarkMaps/sycMap-12 560 2463038 ns/op 308743 B/op 8460 allocs/op
BenchmarkMaps/RWLockMap
BenchmarkMaps/RWLockMap-12 182 6637413 ns/op 40703 B/op 2860 allocs/op
BenchmarkMaps/ConCurrentMap
BenchmarkMaps/ConCurrentMap-12 759 1748355 ns/op 17297 B/op 934 allocs/op
PASS


多读少写场景,ConCurrentMap效率仍然最高,和sync.Map接近。

3.小结 

从测试结果可以看出,分段锁map实现是本次测试中并发效率最高的, 无论是多读少写还是多读多写的场景。在实际业务使用中,建议通过基准测试来考察这3种map,进行选型。


参考阅读

https://zhuanlan.zhihu.com/p/102385081