go 函数结构体参数用值传递还是指针,对性能的影响(原创)

本文探讨了Go语言中,函数参数为struct类型时,值传递和指针传递在不同内存逃逸情况下对性能的影响。在栈空间足够且无逃逸时,两者性能相同;但当变量大到逃逸到堆时,指针传递优于值传递,因为后者会额外开辟内存并增加GC压力。
摘要由CSDN通过智能技术生成

原创,本文实验环境 go1.21.0, GOOS='darwin' 

你一定也有这样的疑惑,函数参数是struct或者返回值是struct的时候,什么时候用指针,什么时候用值传递,我遇到过无脑用指针的公司,也遇到过无脑用值传递的公司。(排除要修改参数内的值,必须使用指针的时候,我们讨论的是两者都可行,对程序效果无bug的情况下),本文专门从性能的角度来探讨一下这个知识点。

考虑到性能,不得不先说一下逃逸:

在程序中,每个函数块都会有自己的内存区域用来存自己的局部变量(内存占用少)、返回地址、返回值之类的数据,这一块内存区域有特定的结构和寻址方式,寻址起来十分迅速,开销很少。这一块内存地址称为栈。栈是线程级别的,大小在创建的时候已经确定,当变量太大的时候,会"逃逸"到堆上,这种现象称为内存逃逸。简单来说,局部变量通过堆分配和回收,就叫内存逃逸。

考虑如下的程序

package main

type T struct {
	b [10*1024*1024]byte
}

func main() {
	var arr T
	fn(arr)
}

func fn(t T) {

}

编译,go build -gcflags='-m' ./main.go ,没有发生逃逸

如果将 T 的成员b数组长度+1,改成 10*1024*1024+1 ,再次编译,会发现,逃逸了:

我们可以看到,当栈空间足够时,不会发生逃逸,但是当变量过大时,已经完全超过栈空间的大小时,将会发生逃逸到堆上分配内存。

(当然,这只是逃逸的一种情况。其他诸如interface{}逃逸,不定长度slice逃逸,我们以后再说。)

那么它对性能有什么影响呢,我们接下来分别进行benchmark测试,本文讨论的是值传递和指针传递,所以先对这两种进行测试。分别讨论有逃逸和没逃逸情况下的值传递和指针传递的表现,

一、在没有逃逸发生的情况下:

package main

import "testing"

type T struct {
	b [10 * 1024 * 1024]byte
}

func fnStack(t T) {}
func fnHeap(t *T) {}

func BenchmarkStack(b *testing.B) {
	var t = T{}
	for i := 0; i < b.N; i++ {
		fnStack(t)
	}
}

func BenchmarkHeap(b *testing.B) {
	var s1 = &T{}
	for i := 0; i < b.N; i++ {
		fnHeap(s1)
	}
}

结果如下,

很明显,两者无差距

二、改变结构体T的大小(b数组长度加1),有逃逸的情况下:

package main

import "testing"

type T struct {
	b [10 * 1024 * 1024+1]byte
}

func fnStack(t T) {}
func fnHeap(t *T) {}

func BenchmarkStack(b *testing.B) {
	var t = T{}
	for i := 0; i < b.N; i++ {
		fnStack(t)
	}
}

func BenchmarkHeap(b *testing.B) {
	var s1 = &T{}
	for i := 0; i < b.N; i++ {
		fnHeap(s1)
	}
}

结果如下:

性能天壤之别。用值传递,要开辟新的内存来存储实参的值,但是又发生了逃逸,只好到堆上去开辟内存,对gc产生了压力。而指针传递省去了开辟新内存的过程,拷贝的过程。给大家看一下值传递发生逃逸下的gc情况:代码:

func BenchmarkStackTrace(b *testing.B) {
	f, err := os.Create("stack.out")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	err = trace.Start(f)
	if err != nil {
		panic(err)
	}
	var t = T{}
	for i := 0; i < b.N; i++ {
		fnStack(t)
	}

	trace.Stop()

	b.StopTimer()
}

结果

差不多每5ms触发一次gc

结论就是:小对象还是尽量栈,只要不逃逸,如果很大的会导致逃逸,那还是直接指针性能更好,具体情况还是得测啊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值