理由如下:
根据内存逃逸的规则,把函数局部变量的指针返回会逃逸,结果导致 gc 压力变大。
所以是否需要尽量返回值,而不返回指针呢?
比如查询一个数据模型返回它,还有其他许多情况。
1
nagisaushio 2023-12-27 23:35:11 +08:00
小类型尽量返回值,大类型看情况
|
2
lianyue 2023-12-27 23:48:31 +08:00
我是 结构体 指针否 值
|
3
rekulas 2023-12-27 23:48:34 +08:00
对看情况 有些情况下返回值可能压力更大
|
4
Maboroshii 2023-12-27 23:52:01 +08:00 via Android
我写 go 基本都有指针,除非是明确返回不可修改的值才用值返回。写 web 应用 gc 压力不存在的,pprof 看下,全耗在序列化上了
|
5
BeautifulSoap 2023-12-28 00:44:14 +08:00 via Android 3
不要过早地进行优化
绝大部分项目的性能要求还没高到需要抠这种细节的地步 |
6
e7 2023-12-28 09:27:04 +08:00
八股就是这么衍生出来的吗。。。大概按 1L 说的就能避免 80%的你认为的不合理,然后真的哪天出现了性能问题再去 pprof ,而且大概率不会是这个原因
|
7
body007 2023-12-28 09:27:57 +08:00 5
结构体统统用指针,普通类型用值。因为结构体你不知道啥时候加需求往里面加大类型,而且结构体一般会到处传,如果所有地方都用值传递,每次赋值都内存拷贝一份也是有开销的,指针的话只拷贝指针地址更快。
另外 https://www.cnblogs.com/janbar/p/17072751.html 这篇文章探讨了直接赋值的深拷贝问题,即使你值传递,结构体内部有指针,那么这些指针在赋值时也是赋值指针地址。如果用值值类型,你到时候还得思考结构体内部哪些字段是深拷贝,哪些是浅拷贝。 http 提供下面方式克隆对象,就是因为值传递内部字段也存在浅拷贝问题,需要编写深拷贝代码。与其值传递增加心智负担,还不如无脑指针传递。 func (r *Request) Clone(ctx context.Context) *Request { 综上所述,我觉得结构体一律用指针。 我在用的 [go-zero]( https://github.com/zeromicro/go-zero/pull/1211/files#diff-a650192c5b74f391823e44c0b326c07abe5c2544ab386b1ce73ce6b293d78a4c) 框架,在这次改动中将参数值传递改为指针传递,导致我某次升级改了好多文件代码,连大佬都觉得结构体指针传递好些吧。 如果你明确的知道你需要值传递,并且清楚这个对象赋值后内部字段存在浅拷贝也不会影响逻辑,那么可以用值传递。 |
8
veightz 2023-12-28 09:41:11 +08:00 via Android
https://go.dev/wiki/CodeReviewComments#pass-values
有时候也不要只看内存尺寸,而忽视了栈性能远快于堆性能,以及用指针带来了内存逃逸开销,是需要综合考虑的。 |
9
veightz 2023-12-28 09:44:03 +08:00 via Android
在写代码的时候,尽量把方法往 值传递优化 和 不可变数据 去设计和实现,对象之海迟早会反噬的…
|
10
vultr 2023-12-28 09:45:26 +08:00
我也认为应该尽量用指针,如果为了减少 GC ,可以用 sync.Pool 重用它。
|
12
8355 2023-12-28 09:53:41 +08:00
我的理解是以业务逻辑为核心,99%的业务返回值,而 1%的业务是很明显需要指针的,对于不能完全理解以上描述的开发者先返回值是没问题的。
|
13
body007 2023-12-28 10:00:32 +08:00
@cheng6563 你得了解 go 的引用类型,chan 是引用类型,直接返回就是引用。作为返回值,引用类型本身类似指针,返回的就是引用。作为参数在引用类型加指针貌似只有需要修改引用类型的时候才用到吧。
|
14
MoYi123 2023-12-28 10:24:18 +08:00
是的, 对于降低 p99 有明显的效果
|
15
Ryans 2023-12-28 10:27:13 +08:00
小型数据结构:如果是小型结构或不频繁修改的数据,优先返回值以减少 GC 压力。
大型或复杂对象:对于大型或需要频繁修改的对象,考虑返回指针以避免大量数据复制。 |
16
keakon 2023-12-28 10:29:35 +08:00
之前看过这篇,性能上大部分情况下是返回 struct 更快:
https://cloud.tencent.com/developer/article/1861199 不过如果要和 nil 区分,或者最终要放到堆里,那就继续用指针。 然后如果是参数的话,记得是超过 32 字节传指针更快,而且每个字段是单独用一个 MOV 指令来复制的,不是一整块复制的。 |
17
chenchengbin 2023-12-28 10:45:38 +08:00
虽然返回指针会导致逃逸,但是存在即合理
|
18
dyllen OP |
19
chenchengbin 2023-12-28 10:47:56 +08:00
@veightz 字符串直接传不会因为传值复制导致内存激增吗,我的理解会复制一个相同大小的字符串
|
20
dyllen OP 比如有些 orm 库的数据库查询,都不是直接返回结果 model 的指针,而是传指针进去,数据填充到里面。
|
22
keakon 2023-12-28 10:51:05 +08:00
@chenchengbin 字符串是浅拷贝 StringHeader
|
23
thinkershare 2023-12-28 10:51:18 +08:00
@chenchengbin 当然不会,golang 的字符串本质上就不是一个纯值类型。
|
24
Maboroshii 2023-12-28 10:52:14 +08:00 via Android
orm 一般用到反射,反射会引起逃逸,所以直接用指针了
@dyllen |
25
Maboroshii 2023-12-28 10:58:06 +08:00
orm 用参数当返回值的另一个点,就是需要调用方来控制这段内存的申请和销毁(在 go 里,你可以用 sync.Pool 来优化)。 也是 C/C++里面的原则吧,谁声明,谁处理, 调用方 malloc 了 ,调用方来 free ,方法内部是不会帮你初始化堆上的内存的。
|
26
chenchengbin 2023-12-28 10:59:46 +08:00
@Maboroshii 都在 orm 上了各种反射
|
27
flmn 2023-12-28 11:32:25 +08:00
尽量用值吧,编译器会帮你优化的
|
28
ZSeptember 2023-12-28 11:38:38 +08:00
尽量用值,除非你的业务量真的到需要用这种细节来优化了,不过真的到时候也是应该先 bench 一下。
我们的业务代码基本不用指针,入参,出参都不用,推荐方法无状态,副作用可控,容易测试。 |
29
szzhiyang 2023-12-28 12:03:48 +08:00
@chenchengbin 字符串(数组)的内部实现: https://go.dev/blog/slices
|
30
chenchengbin 2023-12-28 13:52:01 +08:00
@szzhiyang 擦,一直以为 go 的 string 是复制一个字符串,我知道内部实现是这玩意, 但是也没深入看过, 学习了学习了。。
|