Go高性能编程-不要使用Map缓存大量数据
1、起因
阿森在写代码的时候,有一个通知某个服务器上的所有在线用户的需求,这些用户的信息我是缓存在了Mmap中,大概的代码如下,我在遍历的时候,先是获取读锁,然后for range 遍历。但是这个时候这个节点的用户的上下线还是比较频繁的,大概有几百的qps,这些请求都会获取写锁。这个时候问题就来了。
遍历30w的map居然花了大概10ms,导致后续的写请求都被阻塞。
1 | go复制代码//简单缓存 |
所以我看了下go的map的源码
go/src/runtime/map.go at master · golang/go (github.com)
发现go map的遍历效率相比数组来说是比较差的
- 遍历新 buckets
1. 确认新buckets对应的旧buckets是否还有数据。
2. 如果有,则遍历旧buckets中将分配到该新buckets中的数据;
3. 如果没有,则遍历该新buckets
4. 遍历bucket 中的所有 cell。每个 bucket 中包含 8 个 cell,从有 key 的 cell 中取出 key 和 value
- 遍历新 buckets 中的下一个 bucket,继续以上操作
1 | go复制代码type hmap struct { |
数组的遍历就很好理解了,这里就不上源码了,大概就是找到数组的开始位置直接在连续内存块上扫描
在存储50w数据的时候,数组的遍历效率大概是map的10倍,数量越大效率越高
2、延展
既然数组的遍历效率比map高这么多,那么是否还有其他的性能影响呢?
这个时候我比较了下同样的数量的[]User 和map[int64] User的gc耗时
发现在储存500000
数据量的场景下 数组的gc的耗时居然也比Map快上了5倍
*cpu: m1 pro
*memory: 32G
*Go map gc x 10 cost: 235.258125ms.
*Slice map gc x 10 cost: 43.631375ms.
3、改造
很明显,我们直接用map缓存大量数据的时候,无论是gc还是遍历效率,都是非常低的。
所以我计划用数组和map结合实现一个遍历效率和数组一样,而且插入更新操作和Map一样
1 | go复制代码type KV[K comparable] struct { |
数组里面储存实际的指针,map里面存储key对应的数组的index这样既能获得数组的高效便利和更快GC,还能获得map的快速插入删除更新。代价也很明显内存cost变多了。
你可以在此查看代码和文档:hswell/slicemap (github.com)
能点个赞就最好了
本文转载自: 掘金