4.3 dead code 和调试模式
1. Dead code
In compiler theory, dead code elimination (also known as DCE, dead code removal, dead code stripping, or dead code strip) is a compiler optimization to remove code which does not affect the program results.
Removing such code has several benefits: it shrinks program size, an important consideration in some contexts, and it allows the running program to avoid executing irrelevant operations, which reduces its running time.
死码消除(dead code elimination, DCE)是一种编译器优化技术,用处是在编译阶段去掉对程序运行结果没有任何影响的代码。
死码消除有很多好处:减小程序体积,程序运行过程中避免执行无用的指令,缩短运行时间。
1.1 使用常量提升性能
定义全局变量 a,b
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)
}
}
将a,b
改为常量:
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)
}
}
编译之后查看大小:
$ ls -lh |grep -E 'main_'
1.4M main_const
1.8M main_var
可以看到改成常量之后,体积减小了 0.4 M。
查看编译优化决策:
$ go build -gcflags "-m" main.go
./main.go:5:6: can inline max
./main.go:15:8: inlining call to max
./main.go:16:14: inlining call to fmt.Println
./main.go:16:14: ... argument does not escape
./main.go:16:15: a escapes to heap
可以看到max
函数使用内联编译,即在调用处展开:
func main() {
var result int
if a > b {
result = a
} else {
result = b
}
if result == a {
fmt.Println(a)
}
}
若此时a, b
为常量,则在编译期即可计算:
func main() {
var result int
if 10 > 20 {
result = 10
} else {
result = 20
}
if result == 10 {
fmt.Println(a)
}
}
// 10 > 20 恒为 false
func main() {}
if 20 == 10 {
fmt.Println(a)
}
}
// 20 == 10 恒为 false
func main() {}
可以看到,若全局变量a,b
不是常量而是变量,编译器无法得知变量的值是否能够改变,无法消除 Dead Code。
1.2 可推断的局部变量
若变量的值可以推断时,也会触发编译器优化:
func main() {
var a, b = 10, 20
if max(a, b) == a {
fmt.Println(a)
}
}
$ ls -lh | grep -E "main_"
1.4M main_const
1.4M main_locvar_infer
可以看出触发了Dead code 消除。
若将局部变量放入并发函数中:
func main() {
var a, b = 10, 20
go func() {
b, a = a, b
}()
if max(a, b) == a {
fmt.Println(a)
}
}
$ ls -lh | grep -E "main_"
1.4M main_const
1.8M main_locvar_goroutine
1.4M main_locvar_infer
1.8M main_var
此时无法推断出变量的值了。
2. 调试(Debug)模式
在源代码中,定义全局常量 debug,值设置为 false
,在需要增加调试代码的地方,使用条件语句 if debug
包裹,例:
const debug = false
func main() {
if debug {
log.Println("debug mode is enabled")
}
}
如果是正常编译,常量 debug 始终等于 false
,调试语句在编译过程中会被消除,不会影响最终的二进制大小,也不会对运行效率产生任何影响。
在进行单元测试或者是简单的集成测试时,希望能够执行一些额外的操作,例如打印日志,或者是修改变量的值。提交代码时,再将 debug 修改为 false,开发过程中增加的额外的调试代码在编译时会被消除,不会对正式版本产生任何的影响。
Golang 的标准库中有不少使用这种模式的代码:
go/src [ grep -nr "const debug = false" ] 8:31 下午
cmd/compile/internal/syntax/parser.go:15:const debug = false
cmd/compile/internal/types2/initorder.go:25: const debug = false
cmd/compile/internal/types2/check.go:22:const debug = false // leave on during development
cmd/fix/main.go:47:const debug = false // display incorrectly reformatted source and exit
cmd/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go:24:const debug = false
cmd/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go:35:const debug = false
cmd/vendor/golang.org/x/tools/internal/typeparams/normalize.go:17:const debug = false
cmd/vendor/golang.org/x/tools/internal/facts/facts.go:53:const debug = false
cmd/vendor/golang.org/x/mod/modfile/read.go:673: const debug = false
net/http/transport_test.go:2250: const debug = false
go/internal/gcimporter/gcimporter.go:26:const debug = false
go/types/initorder.go:25: const debug = false
go/types/check.go:23:const debug = false // leave on during development
internal/zstd/block.go:12:const debug = false
2.1 条件编译
使用build tags
可以实现条件编译,此时无需修改代码:
// debug.go, 使用 debug
// +build debug
package main
const debug = true
// release.go 不启用 debug
// +build !debug
package main
const debug = false
// main.go
package main
import "log"
func main() {
if debug {
log.Println("debug mode is enabled")
}
}
$ go build -tags debug -o ./main_debug
$ go build -o ./main_no_debug
$ ./main_no_debug
$ ./main_debug
debug mode is enabled
(备注:// +build
在v1.18
后建议使用//go:build
,两种方式都要留出一个空白行)