【问题标题】:Does Go runtime evaluate the for loop condition every iteration?Go 运行时是否每次迭代都评估 for 循环条件?
【发布时间】:2016-12-26 07:39:44
【问题描述】:

这是《Go 编程语言》一书中的代码 sn-p:

for t := 0.0; t < cycles*2*math.Pi; t += res {
    ...
}

似乎 for 循环条件 t &lt; cycles*2*math.Pi 中的表达式必须在 for 循环的每次迭代之前进行评估。或者,编译器是否通过预先计算表达式的结果来优化它(假设在迭代期间没有变量发生变化)?上述编码风格会影响性能吗?

【问题讨论】:

    标签: performance go compiler-optimization


    【解决方案1】:

    这确实取决于 Go 版本,但 go version go1.7 windows/amd64 似乎确实计算了一次值。

    转码:

    var cycles = 10.0
    var res = 1000.0
    for t := 0.0; t < cycles*2*math.Pi; t += res {
    }
    

    汇编代码:

    movsd   [rsp+58h+var_20], xmm0
    mov     [rsp+58h+var_18], 0
    mov     [rsp+58h+var_10], 0
    lea     rax, qword_494500
    mov     [rsp+58h+var_58], rax
    lea     rax, [rsp+58h+var_20]
    mov     [rsp+58h+var_50], rax
    mov     [rsp+58h+var_48], 0
    call    runtime_convT2E
    mov     rax, [rsp+58h+var_40]
    mov     rcx, [rsp+58h+a] ; a
    mov     [rsp+58h+var_18], rax
    mov     [rsp+58h+var_10], rcx
    lea     rax, [rsp+58h+var_18]
    mov     [rsp+58h+var_58], rax
    mov     [rsp+58h+var_50], 1
    mov     [rsp+58h+var_48], 1
    call    fmt_Println
    movsd   xmm0, cs:$f64_408f400000000000
    movsd   xmm1, [rsp+58h+t]
    addsd   xmm0, xmm1
    movsd   [rsp+58h+t], xmm0
    movsd   xmm1, cs:$f64_404f6a7a2955385e
    ucomisd xmm1, xmm0
    ja      loc_401083
    

    f64_404f6a7a2955385e 是预先计算的双精度值,等于 10 * 2 * math.Pi62.8318530718

    Go 编译器 recently switchedSSA,因此这些优化将不断改进,因为它们从中受益匪浅。目前 SSA 仅在 amd64 上可用:

    编译器工具链

    此版本包括用于 64 位 x86 的新代码生成后端 系统,根据 2015 年的一项提案,该提案一直在开发中 自那时候起。新的后端,基于 SSA,生成更紧凑、更 高效的代码并为优化提供更好的平台,例如 边界检查消除。

    1.8 should have it for all supported architectures:

    编译器工具链

    Go 1.7 为 64 位 x86 系统引入了新的编译器后端。在围棋中 1.8,该后端已进一步开发,现在用于所有 架构。

    【讨论】:

      【解决方案2】:

      似乎没有优化,并且没有在“Compiler And Runtime Optimizations”中列出。

      正如this older discussion中提到的,

      gc 编译器不做任何循环优化。该编译器的主要目标之一是快速编译。虽然改进的优化总是有用的,但它必须符合该目标

      但这可能会改变。 “Recursion And Tail Calls In Go”中说明了一个很好的技术来查看正在发生的事情,您可以在其中查看 go 程序生成的汇编代码。
      另请参阅 2016 年最新的“Reversing GO binaries like a pro”文章。
      go.godbolt.org”也可以提供帮助:请参阅assembly code here

      您可以看到“t &lt; cycles*2*math.Pi”部分总是被评估

      .L2:
              movsd   xmm0, QWORD PTR [rbp-24]
              addsd   xmm0, xmm0
              movsd   xmm1, QWORD PTR .LC2[rip]
              mulsd   xmm0, xmm1
              ucomisd xmm0, QWORD PTR [rbp-8]
              seta    al
              test    al, al
      

      【讨论】:

        【解决方案3】:

        当前的 Go 编译器不会将循环不变的计算移到循环之外。

        编译器的pass列表可以看这里https://github.com/golang/go/blob/master/src/cmd/compile/internal/ssa/compile.go#L329

        在@creker 的示例中,编译器进行了常量折叠,而不是循环不变的代码运动。

        顺便说一句,几个月前我确实为 Go 编译器 https://github.com/golang/go/compare/master...momchil-velikov:dev.chill.licm987654322@做了一个 LCM 通行证

        这在通常使用的 Go 基准测试中并没有显着提高性能。 (我责怪糟糕的寄存器分配:P)

        【讨论】:

        • 当前编译器是 1.7 还是 1.8 beta 2?这是否意味着我的答案比 creker 的更准确?在 1.7 中? 1.8 仍然如此吗?
        • 是的,适用于所有版本,包括主分支。
        • 好的。这似乎证实了我在回答中提到的内容。 +1
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-23
        • 1970-01-01
        • 1970-01-01
        • 2014-06-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多