看了一篇博客,讲 go 不同写法的性能差异,不太理解底层实现

2024-01-25 15:09:18 +08:00
 seekseat

https://mp.weixin.qq.com/s/LIdlk0p32iW2KDjGHGflMQ

这最后怎么得出的 r+0 更快的?什么道理....面试时会被问到吗

2737 次点击
所在节点    Go 编程语言
17 条回复
KaynW
2024-01-25 15:22:12 +08:00
谁问你这个给他两个大逼斗
token10086
2024-01-25 15:24:05 +08:00
茴字有几种写法?
hollc
2024-01-25 15:39:17 +08:00
这种性能差异我觉得其实体现出的是底层实现的 bug ,毫无意义的代码反而效率更高
hxtheone
2024-01-25 15:41:59 +08:00
普通开发者了解 go 编译器里寄存器的使用就可以了, 学会文中这点对日常开发没什么太大帮助, 赞同文章的最后一句话, 这些细节差异应该是编译器处理没必要暴露给开发者
wangritian
2024-01-25 15:53:54 +08:00
面试问这种问题就不是想招好好干活的
看上去是多了+0 之后,触发了编译器某个优化逻辑,让上面循环过程中使用的变量停留在寄存器,而不用写回内存再读出来,确实是编译器内部问题,开发者不用管
rrfeng
2024-01-25 17:25:55 +08:00
开头直接猜编译器优化问题,
看到开始汇编了,以为最后会解释,结果没有??

那这是狗屁文章。。。
rrfeng
2024-01-25 17:53:49 +08:00
无责任猜测:

在 g 里传给 chan 的是 (r+0) 是一个「临时」值,r 没有被传递,因此 r 被优化放到了寄存器上。
而在 f 里要传给 chan 的是变量 r ,所以它不能被优化到寄存器里,只能放到栈上。

所以是寄存器优先原则的作用。
rrfeng
2024-01-25 18:00:13 +08:00
补充一点:文章引用 2 里介绍了 Go 函数参数都是「栈传递」,所以上述解释应该正确
lxdlam
2024-01-25 18:09:59 +08:00
一方面我同意 #3 ,更像是编译器的 bug ,另一方面我本地似乎复现不了这个差距如此巨大的结果,估计可以看下 1.21rc 到现在( go 1.21.6 ) 的差异。

而对于一定程度上的差异( 100000ns per op ),单纯从生成代码上来看,f 生成的函数直接对 r 做了修改,所以需要一次对 r 的 load ,而 g 是对一个临时变量做修改,虽然二者都是一次 load 跟一次 store ,但是 r 毕竟不好说分配在哪儿(也许在 heap 上,也许在 register ,看编译器优化),那么 r 确实可能比起临时变量( go 倾向于分配在 register 上)的读写要更慢。至于为什么会有如此差异,实际上应该是因为编译器识别出来了这个累加 pattern ,而在 f 里因为没有额外操作,所以直接对 r 进行操作,把加数这些都当 immediate value 优化成单次 INCQ 了;而在 g 中,由于又读到了 r + 0 ,编译器首先优化成了将其写入中间变量的操作,又在后续 pass 中发现其实对 r 基本无操作,去掉了这里面所有 r 的主动 reference ,将其完全优化到了完全只读写中间变量,所以生成了这个样子的代码。

以上仅抛砖引玉,我不是 plan9 和 go compiler 专家,只能看个大概,这里面同样可能会有很多说不清的其他因素影响。但是我仍然同意,这种 case 应该 report 给官方去修改,而不是当新时代的语言律师模拟考题,同样,如果在乎这个粒度的性能差距,可能我们会选择更精细的语言和优化方式了,而不是在这继续抄写茴字剩下的写法。
rrfeng
2024-01-25 18:19:01 +08:00
@lxdlam 同意,除了我觉得编译器优化逻辑只要不出错就不是 bug 。毕竟不优化的地方多了去了……

对 f 要优化的话需要分析整个函数里变量的引用关系(诶…怎么变成 rust 了??
pkoukk
2024-01-25 18:19:26 +08:00
同意楼上,这应该是编译器可以优化的问题,不应该作为一个 feature 存在
谁面试问这种问题,就给他两个大逼斗
bybyte
2024-01-25 18:41:07 +08:00
语言层面上这两者完全是等价的吧,底层差异这么大那就是编译器的问题了
mainjzb
2024-01-25 18:49:57 +08:00
这样的编译几乎和 g 一样,应该是堆栈导致,go 的编译器确实垃圾。。对比 rust/C/C++

func g2(n int, c chan<- int) {
r := 0
for i := 0; i < n; i++ {
r += 1
}
tmp := r
c <- tmp
}
mainjzb
2024-01-25 19:03:00 +08:00
进一步信息,这个问题只出现在 1.21 和 1.20 中,其他版本编译没有问题。
根据 https://go.godbolt.org/ 提供的反编译信息
bv
2024-01-25 19:06:28 +08:00
你们上来就争论,却不跑一遍作者的示例代码。
Linux Ubuntu i5-8500 测试结果是:

go1.21.6 下确实如那篇文章所说 g 明显比 f 快。
go1.22rc2 下 g 和 f 效率几乎无差异。
Kumo31
2024-01-25 19:11:58 +08:00
go compiler 的编译优化确实一言难尽,之前测试过,很基础的循环展开都不会做,官方美名其曰「保证编译速度」: https://github.com/golang/go/issues/51302
seekseat
356 天前
学到了,蟹蟹大佬们

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/1011483

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX