Go: 应该使用指针还是结构体副本?

对于许多Go开发人员而言,就性能而言,系统地使用指针共享结构体而不是副本本身似乎是最佳选择。

为了理解使用指针而不是结构体副本的影响,我们将回顾两个用例。

密集分配数据

以下面结构体为例

它可以以副本或者指针方式共享

基于上面2个方法,我们可以写2个性能测试,一个是基于副本方式:

另一个是基于指针方式:

让我们运行上面的测试

测试结果如下

这里使用副本比指针快8倍。

为了理解,让我们看看基于trace生成的图:

                                使用结构体副本时
                                                 使用指针时

第一张图非常简单。因为它没有使用堆,也就没有gc和其他额外的goroutine。

对于第二张图,指针的使用迫使go编译器将变量逃逸到堆中并产生gc压力。如果放大图,我们可以发现gc占据了进程的重要部分:

我们可以发现gc每4ms执行一次。

如果我们再次放大,我们可以发现更多细节:

蓝色,粉红色和红色是垃圾收集器的阶段,而棕色的与堆上的分配有关(在图中用“ runtime.bgsweep”标记):

即使这个例子有点极端,我们也可以看到在堆而不是栈上分配变量的代价是多么昂贵。 在我们的示例中,在栈上复制结构体比堆上共享指针快的多。

如果我们限制处理器为1(GOMAXPROCS=1),情况将会更糟:

堆分配测试从75ns/op 降低到 114ns/op。

密集方法调用

对于第二个用例,我们将向结构中添加两个空方法:

使用副本方式调用:

 

使用指针方式调用:

和预期大相径庭的结果:

结论

在go中使用指针而不是结构的副本并不总是件好事。

本文编译自  Go: Should I Use a Pointer instead of a Copy of my Struct?

发表评论

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

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据