4.3 dead code 和调试模式

Kesa...大约 3 分钟golang

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

(备注:// +buildv1.18后建议使用//go:build,两种方式都要留出一个空白行)

Reference

  1. Dead code elimination - wikipediaopen in new window
  2. https://geektutu.com/post/hpg-dead-code-elimination.htmlopen in new window
上次编辑于:
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.2