关于后端:读一例-Go-编译器代码优化-bug-定位和修复解析

9次阅读

共计 1583 个字符,预计需要花费 4 分钟才能阅读完成。

看到一例 Go 编译器代码优化 bug 定位和修复解析这样一篇文章, 感觉有些意思. 在此复现和记录

在 Go 1.16 版本下, 是没有这个 bug 的 (已修复). 参照 gvm: 灵便的 Go 版本管理工具 将 Go 版本切至有问题的 1.13.5(或 1.14.6)

➜  go version
go version go1.13.5 darwin/amd64

package main

import "fmt"

func main() {sli := []int32{1, 2, 3, 4, 5, 6}

    // 如果是这种形式申明的 sli, 仍然会出 bug
    //var sli []int32
    //sli = []int32{1, 2, 3, 4, 5, 6}

    // 如果是这种形式申明的 sli, 则不会出 bug
    //sli := make([]int32, 0)
    //sli = append(sli, 1, 2, 3, 4, 5, 6)

    for k, v := range sli {// 如果把 sli 改为 []int32{1, 2, 3, 4, 5, 6}, 也不会出 bug

        if k+1 < 1 { // 去掉这个不会被执行进, 没啥用的判断, 则也不会出 bug; 改为 if k+2 < 2 {, 也不会出 bug
            panic("")
        }

        fmt.Println("=========")

        fmt.Println(k, v)
    }
}

执行后果:

=========
0 1
=========
1 2
=========
2 3
=========
3 4
=========
4 5
=========
5 6
=========
6 622680
=========
7 192
=========
8 17477952
=========
9 0
=========
10 17733712
=========
11 0
=========
12 17475456
=========
13 0
=========
14 622792
=========
15 192
=========
16 17475584

...

在线 查看 & 比对 给定程序编译产出的汇编后果. 该网站好评!

其实 Go 的编译器的实现中规中矩,相比于 GCC/Clang 等老牌编译器甚至有些简陋,许多优化并未实现

“Go 编译器提供了十分不便的性能,能够查看各个优化 pass 前后的 SSA IR,只须要在编译时,减少一个 GOSSAFUNC=xxx 环境变量即可,xxx 即为想要剖析的函数的名字,因为 Go 编译器外部的优化都是函数级别的。比方上图的例子,只须要运行 GOSSAFUNC=main go build ssaexample.go,编译器就会将 SSA IR 后果输入到当前目录的 ssa.html 中,用浏览器关上即可。”

用浏览器关上当前目录的 ssa.html:

执行 GOSSAFUNC=main go build 1.go

浏览器关上:

prove pass 的性能是对全局中 SSA 值的取值范畴做一个推断, 这样就能够打消掉许多不必要的分支判断

Go 是内存平安的语言,所以所有的 slice 取元素操作都须要做一个查看,来判断取元素用的下标是否超出了 slice 的范畴,这个操作叫做 bound check。然而实际上,很多代码中在编译期就能确定这个下标是否越界,那么咱们就能够将本来须要在运行期做 bound check 的查看给打消掉,这步优化叫做 bound check elimination (即 BCE)

如下 这样的写法在 Go 源码中十分多

可参考 Go 官网规范编译器中所做的优化 之 Bounds Check Elimination

通过日志中的关键字, 能找到只有 findIndVar 和 addLocalInductiveFacts 这两个函数中会打这条日志,联合上下文和相干正文不难看出实际上问题是出在 addLocalInductiveFacts 这个函数上。addLocalInductiveFacts 具体是什么性能呢?从正文中不难看出,这里的性能是匹配到一种非凡的代码 pattern,即相似 repeat until 的逻辑,在循环开端判断某个条件是否成立

修复

具体

本文由 mdnice 多平台公布

正文完
 0