V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
seekseat
V2EX  ›  Go 编程语言

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

  •  
  •   seekseat · 331 天前 · 2703 次点击
    这是一个创建于 331 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

    那这是狗屁文章。。。
    rrfeng
        7
    rrfeng  
       331 天前   ❤️ 1
    无责任猜测:

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

    所以是寄存器优先原则的作用。
    rrfeng
        8
    rrfeng  
       331 天前
    补充一点:文章引用 2 里介绍了 Go 函数参数都是「栈传递」,所以上述解释应该正确
    lxdlam
        9
    lxdlam  
       331 天前
    一方面我同意 #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
        10
    rrfeng  
       331 天前 via Android
    @lxdlam 同意,除了我觉得编译器优化逻辑只要不出错就不是 bug 。毕竟不优化的地方多了去了……

    对 f 要优化的话需要分析整个函数里变量的引用关系(诶…怎么变成 rust 了??
    pkoukk
        11
    pkoukk  
       331 天前
    同意楼上,这应该是编译器可以优化的问题,不应该作为一个 feature 存在
    谁面试问这种问题,就给他两个大逼斗
    bybyte
        12
    bybyte  
       331 天前
    语言层面上这两者完全是等价的吧,底层差异这么大那就是编译器的问题了
    mainjzb
        13
    mainjzb  
       331 天前
    这样的编译几乎和 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
        14
    mainjzb  
       331 天前
    进一步信息,这个问题只出现在 1.21 和 1.20 中,其他版本编译没有问题。
    根据 https://go.godbolt.org/ 提供的反编译信息
    bv
        15
    bv  
       331 天前
    你们上来就争论,却不跑一遍作者的示例代码。
    Linux Ubuntu i5-8500 测试结果是:

    go1.21.6 下确实如那篇文章所说 g 明显比 f 快。
    go1.22rc2 下 g 和 f 效率几乎无差异。
    Kumo31
        16
    Kumo31  
       331 天前
    go compiler 的编译优化确实一言难尽,之前测试过,很基础的循环展开都不会做,官方美名其曰「保证编译速度」: https://github.com/golang/go/issues/51302
    seekseat
        17
    seekseat  
    OP
       323 天前
    学到了,蟹蟹大佬们
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2655 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 06:54 · PVG 14:54 · LAX 22:54 · JFK 01:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.