golang 中 map 性能优化[低阶]

简单介绍

golang 中的 build-in 的 map 这个 map 是非线程安全的,但是也是最常用的一个家伙。 为了测试多个 map 的性能我写了个接口 Map

然后这是封装的普通的 map

别看一堆代码,其实就是 get 和 set 操作。在这里我们要使用 golang 自带的 test 工具

这其中有个变量 Writer 就是写者的数量,如果只有 1 的时候程序能安全运行退出

但是一旦我们把 Writer 数量改为 2

立马就爆炸了。那么????golang 自己官方心理没数么?

当然有数 golang 开发者其中之一可是拿图灵奖的。你可以点击stackoverflow 上的讨论[1]github 这里[2]去查看相关的 issue

Sync. Map

这是某大佬提出的解决方案,我们试试

我简单封装了一下,测试个性能没啥问题。

现在把 Write 增加也没问题了,可是真的没问题么?

我们现在小改一下第一种 map 加了个 RW 锁,然后和这种 map 做一下比较看看?

然后我们这次用 Test 里的 Benchmark 试试看,为了方便比较,我们写一个函数 benchmarkMap。

首先是 BenchMark 的函数当使用

的时候会被调用,然后来测试两种 Map 性能,上面那个是测试性能的函数,分别对两个函数的进行测试~~拭目以待

当两者都是 100 的时候

基本上 SyncMap 的整体性能是优于 mapWithRWLock 的我来分析一下为什么

从古至今,人们一直在时间和空间上做斗争,这次也不例外,两种锁的实现原理不一样。

图1:带锁的 map

当我们使用普通 Map 带 RWMutex 会将整块内存锁住,然后其他请求就要等待。 SyncMap 是如何实现的呢?

图2:SyncMap

它分为两块内存(存的都是指针),一块只读区域,一块 Dirty 区域支持读写。

两边的指针指向原数据,当需要 Get 的时候他会执行 Load 操作从 Read 中去获取指针指向的值,如果没有找到( miss )发生了,就转而会去 dirty 中获得数据并且存入 Read 中。

miss 超过一定数量的时候,他就会用原子操作把 dirty 的数据 Promote 到 ReadOnly 中。

因此 Sync 这种机制,往往只适用于 Key-Value 相对稳定的业务情况,读多写少的业务。

手痒想写个内存的看看到底多花多少内存 go tool pprof 是一个工具可以查看代码测评产生的内存日志

不用说了这看起来三倍的内存消耗,果然越快内存越大。那么?本次测评到此结束?

!!! 并没有!!! 还有一个大佬写了个 concurrent-map 甚叼,我们来观摩一波。concurrent-map[3]

立马封装一波

迫不及待开始测试,当 Write=100,Reader=100 的时候

那么我同样做个表格吧,把读写的几种情况都列出来

R/WSyncMapmap_with_RWLockCMap

最后说一下这个并发读map是怎么搞的

图2:SyncMap

左边是普通的map,当有读写的时候锁上了,其他线程就无法读写了。右边的是 concurrentMap ,他利用了一种 partition 的思想,把 Map 的内存 SHARD (分割)成N份,然后用不同的 锁上锁,那么降低了需要资源被锁的概率。

我们在日常中编程的时候容易陷入一种误区,就是这锁,那锁,全锁上,面试也在问各种锁,但是在真实QPS比价高的业务中,锁是一种很可怕的东西,如果能在编程的时候好好想想写出 **LockFree`的程序是最好的啊。

我是北京某211的混子,从19年10月开始写两行golang到现在不知不觉已经过去了2个月,上手就开始拉框架写代码的我已经进化到开始分析性能,然后优化代码啦,如果有小伙伴想一起讨论讨论,欢迎。

参考资料

[1]

stackoverflow这里:

[2]

github 上的讨论:

[3]

concurrent-map: