众所周知,Golang中应该尽可能的使用值传递,而不是指针传递,除非结构体非常大,原因有以下几个:

  • 使用指针会使逃逸分析认为内存应该分配到堆上
  • 分配在栈上会随着堆栈的回收而回收,分配在堆上会增加GC的压力(每次多扫描很多指针)
  • 在栈上分配内存的性能和栈上内存拷贝的性能都比较好

其中第二点,特别是对于Slice,其中使用指针会大大增加GC的扫描压力,增加GC耗时。对于map来说,也同样不应该使用指针。

go 1.5版本之后,如果你使用的map的key和value中都不包含指针,那么GC会忽略这个map。

扩展阅读:[1] [2]

那么问题来了,既然我们在C++中习惯性的使用指针来减少拷贝,在Golang中会带来副作用,那这个副作用到底有多大呢?在多大的结构中使用指针才能抵消他带来的副作用呢?本文简单的测试下

待测试代码

测试代码

性能对比

为了防止内联和优化,先编译再测试。

测试时并未关本机上其他程序,所以也许会有些许偏差,不过应该影响不大。

可以看到对于简单的这个struct,拷贝一次的话,性能差距有10倍以上了。

抵消副作用

为了达到我们使用指针减少拷贝的目的,抵消GC和申请堆内存的耗时,必须构造一个包含很多元素的struct。而其中的元素也不能是string或者slice,因为他们其实数据都是建在堆上的。而string类型设计时是不可修改的,拷贝时只有在修改时才会重新申请内存。所以struct内的元素在测试时刨除这种类型的影响,我就暂时只想到了拿很多个int64来测试。

生成了一堆变量:

测试时有两个变量,一个是struct的大小,这里也就是int64的数量,另一个是拷贝的次数,需要调整几次分别测试。

Benchmark测试代码:

编译并测试:

测试结果

方便对比,建个表格

5*int64

首先是5个int64的struct对比,直接看图。可以看到拷贝那么多次也还是值拷贝效率高……

拷贝次数 5*int64 Struct 5*int64 Pointer
5 11.7 30.4
10 19.2 36.3
15 27.1 41.6
20 37.6 47.3
30 51.8 59.7
40 80.6 91.2
60 103 117
100 165 172

5个int64的struct

10*int64

这么大一般就是平时用到的比较大的struct的大小了,可以看到还需要拷贝15次左右,指针才会效率更好。

拷贝次数 10*int64 Struct 10*int64 Pointer
5 23.3 35
10 36 40.2
15 49 45.8
20 69.5 51
30 98.4 63.5
40 124 95.1

10个int64的struct

15*int64

拷贝次数 15*int64 Struct 15*int64 Pointer
5 27.2 39.8
10 44.8 44.6

100*int64

拷贝次数 100*int64 Struct 100*int64 Pointer
5 110 121

结果还是比较出乎我的意料,可以看到100个int64大小的struct,在平时正常使用时一般都小于5次拷贝,这样依旧是值拷贝略微更优。

这么看来似乎几乎根本不需要用到指针了。除非在某些必然会逃逸的对象(例如interface化了),或者希望函数内能修改其值的一些情况下,才会使用到指针传递。

评论

电子邮件地址不会被公开。 必填项已用*标注

你可以使用以下 HTML 标签和属性:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">