Dawn's Blogs

分享技术 记录成长

0%

Go语言高性能编程 (7) 编译优化——死码消除

死码消除

什么是死码消除

死码消除(dead code elimination,DCE)是一种编译器优化技术,用处是在编译阶段去掉对程序运行结果没有任何影响的代码

死码消除有很多好处:减小程序体积,程序运行过程中避免执行无用的指令,缩短运行时间。

应用

全局常量 vs 全局变量

在某些情景下使用全局常量替换全局变量,性能可能会有很大的提升。

在使用常量时,在某些情况下编译器可以直接得到计算结果,进而可以死码消除

死码消除删除了不必要的分支和语句,不仅二编译后的二进制文件体积减小,而且因为可能少了一些判断分支的条件所以效率也会提升

有以下两个文件:

maxvar.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// maxvar.go
func max(num1, num2 int) int {
if num1 > num2 {
return num1
}
return num2
}

var a, b = 10, 20

func main() {
if max(a, b) == a {
fmt.Println(a)
}
}

maxconst.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// maxconst.go
func max(num1, num2 int) int {
if num1 > num2 {
return num1
}
return num2
}

const a, b = 10, 20

func main() {
if max(a, b) == a {
fmt.Println(a)
}
}

在编译两个文件之后,发现 maxconst 比 maxvar 的二进制大小少了 10%。

在编译时:

  • 首先 max 函数被内联在了 main 函数内部。
1
2
3
4
5
6
7
8
9
10
11
func main() {
var result int
if 10 > 20 {
result = 10
} else {
result = 20
}
if result == 10 {
fmt.Println(a)
}
}
  • 如果 a 和 b 为常量,在编译时就可以直接计算,得到结果。进而分支消除
1
2
3
4
5
func main() {
if 20 == 10 {
fmt.Println(a)
}
}
  • 又 20 == 10 可以直接计算出来永远为假,所以再次分支消除
1
func main() {}

局部变量

在上述例子中,如果将全局变量改为局部变量,编译器依然可以在编译阶段计算得到值,死码消除依然会生效

但是如果涉及到了并发操作,则死码消除会失效

包级别的变量和函数内部的局部变量的推断难度是不一样的

函数内部的局部变量的修改只会发生在该函数中。但是如果是包级别的变量,对该变量的修改可能出现在:

  • 包初始化函数 init() 中,init() 函数可能有多个,且可能位于不同的 .go 源文件。
  • 包内的其他函数
  • 如果是 public 变量(首字母大写),其他包引用时可修改

推断 package 级别的变量是否被修改难度是非常大的,从上述的例子看,Go 编译器只对局部变量作了优化