Golang 中map与GC“纠缠不清”的关系

  • Map是什么?
  • GC是什么?
    • GC定义
    • GC中的垃圾怎么定义
    • Map需要被GC回收吗?
    • 什么场景下会需要Map避开GC?
    • 究竟影响了多少指标?
    • 不同指标下的结果量化统计与分析
      • 结论1
      • 结论2
  • 基于GC逃逸的map缓存设计
  • 思考

Map是什么?

map的定义:是能够在时间复杂度为O(1)的情况下,对目标数据进行添加、删除、查询的一种哈希表,采用的格式为Key-Value存储,每一个value都对应的有一个key

GC是什么?

GC定义

GC 是一种垃圾收集器,目标对象是用户程序在运行过程中的堆,也就是heap,旨在清楚程序运行完之后产生的垃圾,释放内存以便循环利用

GC中的垃圾怎么定义

一般情况来说内存分为堆和栈。栈的话是由操作系统和编译器来进行垃圾回收(一般栈出问题了都是比较严重的事故),堆的话则是用程序来进行动态分配的,这里的“垃圾“主要指的就是在栈中、被分配的、且在函数执行的周期中没有再被引用的对象

Map需要被GC回收吗?

不一定,参考go 1.5的解释
After go version 1.5, if you use a map without pointers in keys and values, the GC will omit its content.
指针类型的数据结构包括啥? string类型,结构体类型,或者任何基本类型+指针的定义(*int, *float),甚至可以是返回类型为指针的function
原理猜想:可能的原因是非指针的类型,例如int32,uint32,不会被分配到堆上,从而避免了被GC扫到。

什么场景下会需要Map避开GC?

这个问题等价于GC在什么场景下会比较影响效率,通常情况下,GC是对栈进行操作,并且在进行GC的时候会停掉整个程序的运行(但是在Go 1.5之后有concurrent GC,这里不做讨论),那么实际上影响整个程序运行效率的实际上就是GC运行的时长,而GC运行的时长主要与程序动态分配的堆内存有关,所以可以推测:在申请了大量的堆内存的场景,例如设计百万数量,甚至千万数量级别的内存缓存中,我们需要考虑GC回收所占用的延时。

究竟影响了多少指标?

让我们做个实验,探究在不同数据量+使用不同KV存储类型的map的变量条件下,GC的时间长短。
数据量:
三个水位:十,一百,一千万
map的kv类型有

var demo1 = make(map[int]*int)// KV类型->对应有指针的map
var demo2 = make(map[int]int) // KV类型->对应无指针的map
type demoWithOneE = struct {E int
}
type demoWithTwoE = struct {E intE1 int
}
type demoWithThreeE = struct {E intE1 intE2 int
}

相关测试逻辑:
从1到N(N=10,100,1000000)分别给map赋值,赋值完成之后进行runtime.GC(),并记录GC的时间

// TestForPointerMap 测试指针map
func main() {TestForPointerMapBenchMark()TestForNonPointerMapBenchMark()
}// TestForStructPointerMapBenchMark 测试结构体指针map
func TestForStructPointerMapBenchMark(wg *sync.WaitGroup) {defer wg.Done()runtime.GC()var demo1 = make(map[int]*demo) // KV类型->对应有指针的mapfor i := 0; i < N; i++ {v := i... // 这里分别初始化三种不同的结构体,每种结构体的成员变量类型相同,个数不同}// 进行runtime.GC(),并统计时间println("开始统计")start := time.Now()runtime.GC() // 开启这一轮的GCresult := time.Since(start)println(fmt.Sprintf("GC time: %v", result))
}// TestForPointerMapBenchMark 测试指针map
func TestForPointerMapBenchMark() {runtime.GC()var demo1 = make(map[int]*int) // KV类型->对应有指针的mapfor i := 0; i < N; i++ {v := idemo1[i] = &v}// 进行runtime.GC(),并统计时间println("开始统计")start := time.Now()runtime.GC() // 开启这一轮的GCresult := time.Since(start)println(fmt.Sprintf("GC time: %v", result))
}// TestForNonPointerMapBenchMark 测试指针map
func TestForNonPointerMapBenchMark() {runtime.GC()var demo2 = make(map[int]int) // KV类型->对应无指针的mapfor i := 0; i < N; i++ {demo2[i] = i}println("开始统计")// 进行runtime.GC(),并统计时间start := time.Now()runtime.GC() // 开启这一轮的GCresult := time.Since(start)println(fmt.Sprintf("Non GC time: %v", result))
}

不同指标下的结果量化统计与分析

结果 无指针(int) 有指针(int) 有指针(demoWithOneE) 有指针(demoWithTwoE) 有指针(demoWithThreeE)
10 103.76µs 122.167µs 148.924µs 111.608µs 151.322µs
1000 235.812µs 209.053µs 150.502µs 136.332µs 109.873µs
10000000 1.283464ms 11.629275ms 11.370434ms 20.11954ms 31.112292ms
结论1

随着KV数量级的增加,带指针的map需要更多申请更多的堆空间,因此程序需要申请GC需要更多的时间来进行垃圾回收

结论2

在有指针的前提下,可以看到含有不同成员变量的结构体,所占用的GC时间也不相同,但从数据可以看出:
在大数据量的情况下,GC回收时间与结构体中的成员变量数量呈线性关系。原因是在结构体中,成员数量越多,单个结构体需要申请的内存就越大。
不过目前没有尝试过结构体中多种不同的成员变量类型的组合,但猜想由于内存对齐,实际实验结果也会有类似的结论

基于GC逃逸的map缓存设计

通常情况下map中使用string等指针类型的数据结构有助于简化开发成本,同时也能解决大部分问题。但是在数百万甚至千万级别的内存缓存中,GC的周期回收时间可以达到秒级
因此,我们需要尽量避免map中存在指针,解决的办法可以采用“二级索引”的思想:
我们动态申请内存(此处基于不同的逐出策略,需要做一定的内存申请策略的调整),之后建立map[int]int key为int表示的是逻辑key(可以定义为string类型,也可以定义为函数类型等等带指针的数据结构)的哈希函数,其中value存放的为真正数据在内存中的offset。
例如,我们插入一个<"hello","world">的KV值,首先先将hello哈希成一串整形数字,再将world转化为byte存储在我们申请的内存中,由于world占用5个byte,所以offset=5

思考

  • 对于没有指针的map对应的case,其GC的回收时间也随着KV数量的增大而增大,此处不符合预期,目前还在研究中。

Golang 中map与GC“纠缠不清”的关系相关推荐

  1. golang 中 map 转 struct

    golang 中 map 转 struct package mainimport ("fmt""github.com/goinggo/mapstructure" ...

  2. golang 中 map 排序

    golang 中没有专门的 map 排序函数,且 map 默认是无序的,也就是你写入的顺序和打印的顺序是不一样的. m := make(map[string]string, 0) m["on ...

  3. golang中map并发读写问题及解决方法

    这是一个创建于 2017-03-05 06:02:54 的文章,其中的信息可能已经有所发展或是发生改变. 一.map并发读写问题 如果map由多协程同时读和写就会出现 fatal error:conc ...

  4. golang 中map 和slice 索引速度比较

    主文件 package mainvar max = 100 var Slice = make([]int, max+10) var Map = make(map[int]int)func init() ...

  5. 【golang学习总结】10 golang中map用法

    本文介绍SpringBoot相关内容.和[跨考菌]一起加油吧~ 如果你有收获,记得帮博主一键三连哦

  6. Golang 中 map 探究

    动手点关注 干货不迷路 

  7. golang key map 所有_Map的底层实现 为什么遍历Map总是乱序的

    Golang中Map的底层结构 其实提到Map,一般想到的底层实现就是哈希表,哈希表的结构主要是Hashcode + 数组. 存储kv时,首先将k通过hashcode后对数组长度取余,决定需要放入的数 ...

  8. Golang笔记——map

    map 的基本介绍 map 是 key-value 数据结构,又称为字段或者关联数组.类似其它编程语言的集合, 在编程中是经常使用到 map 的声明 基本语法 var map 变量名 map[keyt ...

  9. golang对map的理解

    一.map的基本介绍 map 是 key-value 数据结构,又称为字段或者关联数组.类似其它编程语言的集合 二.map的声明 var map 变量名 map[keytype]valuetyp ma ...

  10. golang对map排序

    golang中map元素是随机无序的,所以在对map range遍历的时候也是随机的,不像php中是按顺序.所以如果想按顺序取map中的值,可以采用以下方式: import ("fmt&qu ...

最新文章

  1. python读xml文件生成头文件_Python根据指定文件生成XML的方法
  2. css transition兼容性,CSS3 Transition详解和使用
  3. java7 AIO初体验
  4. 课程设计完成之后要考虑的问题
  5. 一行代码完成定时任务调度,基于Quartz的UI可视化操作组件 GZY.Quartz.MUI
  6. linux 打包解压
  7. python构建二叉树_python--使用递归的方式建立二叉树
  8. java break 在if 中使用_Java | 使用JNA在Java中实现cls(cmd清屏)功能
  9. lc300.最长递增子序列
  10. atlas 力矩计算_Atlas Copco基本拧紧技术
  11. 阶段2 JavaWeb+黑马旅游网_15-Maven基础_第1节 基本概念_01maven概述
  12. TeeChart安装教程
  13. 初生牛犊不怕虎:年轻人的成长之路
  14. win10计算机丢失msvcr,win10计算机丢失MSVCR120文件怎么办
  15. 为什么catagory可以增加成员方法,不可以增加成员变量
  16. 《铸梦之路》帧同步卡牌放置手游(斗罗大陆武魂觉醒、上古王冠)
  17. 高德地图的基础使用(一)显示地图
  18. Java用“埃氏筛法”求素数
  19. spring cloud contract的应用实现与概念理解-服务请求者一侧的落地-细节较多避免踩坑卡壳
  20. 芸芸众生中的一个过客

热门文章

  1. Flink电商实时数仓项目04-DWS层
  2. 计算机论文答辩2分钟演讲稿,关于毕业论文答辩演讲稿9篇
  3. 光源基础知识及光源选型
  4. 关于微信8.0.0以下版本登录版本验证的解决办法
  5. 如何在计算机上增加一个磁盘分区,电脑怎么添加硬盘分区
  6. 测试用例的设计要素以及设计测试用例的方法
  7. 深度学习#1.有监督学习和无监督学习
  8. SpringCloud Alibaba实战第三课 Gateway、Sentinel实战
  9. C++复数运算符重载,复数开平方
  10. Python调用百度地图api查询经纬度