惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News | PayPal Newsroom
云风的 BLOG
云风的 BLOG
GbyAI
GbyAI
Engineering at Meta
Engineering at Meta
B
Blog RSS Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Register - Security
The Register - Security
L
LangChain Blog
A
About on SuperTechFans
S
Schneier on Security
博客园 - 三生石上(FineUI控件)
Stack Overflow Blog
Stack Overflow Blog
The Hacker News
The Hacker News
AWS News Blog
AWS News Blog
博客园 - 司徒正美
Scott Helme
Scott Helme
K
Kaspersky official blog
Cyberwarzone
Cyberwarzone
T
Tenable Blog
腾讯CDC
Recorded Future
Recorded Future
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
G
GRAHAM CLULEY
Security Latest
Security Latest
S
Securelist
D
Darknet – Hacking Tools, Hacker News & Cyber Security
aimingoo的专栏
aimingoo的专栏
Google DeepMind News
Google DeepMind News
V
Vulnerabilities – Threatpost
雷峰网
雷峰网
T
The Exploit Database - CXSecurity.com
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
V2EX
T
The Blog of Author Tim Ferriss
D
Docker
S
Security Affairs
F
Full Disclosure
Know Your Adversary
Know Your Adversary
N
News and Events Feed by Topic
N
News and Events Feed by Topic
T
Tor Project blog
Hugging Face - Blog
Hugging Face - Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Microsoft Security Blog
Microsoft Security Blog
Simon Willison's Weblog
Simon Willison's Weblog
Recent Announcements
Recent Announcements
博客园_首页
博客园 - 聂微东
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
Security @ Cisco Blogs

极客兔兔

Go sync.Cond | Go 语言高性能编程 Go sync.Once | Go 语言高性能编程 Go 逃逸分析 | Go 语言高性能编程 2020 年终总结 | 极客兔兔 Go struct 内存对齐 | Go 语言高性能编程 Go 空结构体 struct{} 的使用 | Go 语言高性能编程 控制协程(goroutine)的并发数量 | Go 语言高性能编程 | 极客兔兔 如何退出协程 goroutine (其他场景) | Go 语言高性能编程 如何退出协程 goroutine (超时场景) | Go 语言高性能编程 Go 语言陷阱 - 数组和切片 | Go 语言高性能编程 减小 Go 代码编译后的二进制体积 | Go 语言高性能编程 Go Reflect 提高反射性能 | Go 语言高性能编程 读写锁和互斥锁的性能比较 | Go 语言高性能编程 | 极客兔兔 for 和 range 的性能比较 | Go 语言高性能编程 切片(slice)性能及陷阱 | Go 语言高性能编程 | 极客兔兔 字符串拼接性能及原理 | Go 语言高性能编程 | 极客兔兔 pprof 性能分析 | Go 语言高性能编程 benchmark 基准测试 | Go 语言高性能编程 Go 语言高性能编程 | 极客兔兔 Go 接口型函数的使用场景 | 极客兔兔 Python 简明教程 | 快速入门 | 极客兔兔 Go 语言笔试面试题(代码输出) | 极客面试 | 极客兔兔 动手写RPC框架 - GeeRPC第七天 服务发现与注册中心(registry) | 极客兔兔 动手写RPC框架 - GeeRPC第六天 负载均衡(load balance) 动手写RPC框架 - GeeRPC第五天 支持HTTP协议 | 极客兔兔 动手写RPC框架 - GeeRPC第四天 超时处理(timeout) | 极客兔兔 动手写RPC框架 - GeeRPC第三天 服务注册(service register) 动手写RPC框架 - GeeRPC第二天 支持并发与异步的客户端 | 极客兔兔 动手写RPC框架 - GeeRPC第一天 服务端与消息编码 | 极客兔兔 7天用Go从零实现RPC框架GeeRPC | 极客兔兔 Go 语言笔试面试题(并发编程) | 极客面试 | 极客兔兔 Go 语言笔试面试题(基础语法) | 极客面试 | 极客兔兔 Go 语言笔试面试题汇总 | 极客面试 | 极客兔兔 Go Context 并发编程简明教程 | 快速入门 Go Mmap 文件内存映射简明教程 | 快速入门 动手写ORM框架 - GeeORM第七天 数据库迁移(Migrate) | 极客兔兔 动手写ORM框架 - GeeORM第六天 支持事务(Transaction) | 极客兔兔 动手写ORM框架 - GeeORM第五天 实现钩子(Hooks) | 极客兔兔 动手写ORM框架 - GeeORM第四天 链式操作与更新删除 | 极客兔兔 动手写ORM框架 - GeeORM第三天 记录新增和查询 | 极客兔兔 动手写ORM框架 - GeeORM第二天 对象表结构映射 | 极客兔兔 动手写ORM框架 - GeeORM第一天 database/sql 基础 SQLite 常用命令 | 速查表(Cheat Sheet) 7天用Go从零实现ORM框架GeeORM | 极客兔兔 动手写分布式缓存 - GeeCache第七天 使用 Protobuf 通信 动手写分布式缓存 - GeeCache第六天 防止缓存击穿 | 极客兔兔 动手写分布式缓存 - GeeCache第五天 分布式节点 | 极客兔兔 动手写分布式缓存 - GeeCache第四天 一致性哈希(hash) | 极客兔兔 Go Mock (gomock)简明教程 | 快速入门 动手写分布式缓存 - GeeCache第三天 HTTP 服务端 动手写分布式缓存 - GeeCache第二天 单机并发缓存 | 极客兔兔 Go Test 单元测试简明教程 | 快速入门 7天用Go从零实现分布式缓存GeeCache | 极客兔兔 Go WebAssembly (Wasm) 简明教程 | 快速入门 Go RPC & TLS 鉴权简明教程 | 快速入门 Go Protobuf 简明教程 | 快速入门 Go语言动手写Web框架 - Gee第七天 错误恢复(Panic Recover) WSL, Git, Mircosoft Terminal 等常用工具配置 Rust 简明教程 | 快速入门 | 极客兔兔 Go语言动手写Web框架 - Gee第六天 模板(HTML Template) 百宝箱 - 值得收藏的工具网站 | 极客兔兔 Go语言动手写Web框架 - Gee第五天 中间件Middleware | 极客兔兔 Go语言动手写Web框架 - Gee第四天 分组控制Group | 极客兔兔 Go语言动手写Web框架 - Gee第三天 前缀树路由Router | 极客兔兔 博客折腾记(七) - Gitalk Plus | 极客兔兔 Go语言动手写Web框架 - Gee第二天 上下文Context | 极客兔兔 Go2 新特性简明教程 | 快速入门 | 极客兔兔 博客折腾记(六) - 不要为了流量忘记了初心 | 极客兔兔 Go语言动手写Web框架 - Gee第一天 http.Handler | 极客兔兔 7天用Go从零实现Web框架Gee教程 | 极客兔兔 Go Gin 简明教程 | 快速入门 Go 语言简明教程 | 快速入门 | 极客兔兔 机器学习笔试面试题 11-20 | 极客面试 | 极客兔兔 机器学习笔试面试题 1-10 | 极客面试 | 极客兔兔 机器学习笔试面试题汇总 | 极客面试 | 极客兔兔 TensorFlow 2 中文文档 - RNN LSTM 文本分类 TensorFlow 2 中文文档 - TFHub 迁移学习 TensorFlow 2 中文文档 - 卷积神经网络分类 CIFAR-10 TensorFlow 2 中文文档 - 保存与加载模型 TensorFlow 2 中文文档 - 过拟合与欠拟合 TensorFlow 2 中文文档 - 回归预测燃油效率 TensorFlow 2 中文文档 - 特征工程结构化数据分类 TensorFlow 2 中文文档 - IMDB 文本分类 TensorFlow 2 中文文档 - MNIST 图像分类 TensorFlow 2 / 2.0 中文文档 TensorFlow 2.0 (九) - 强化学习 70行代码实战 Policy Gradient 博客折腾记(五) - 友链这件事,没那么简单 | 极客兔兔 博客折腾记(四) - 原创资格是争取来的 | 极客兔兔 TensorFlow 2.0 (八) - 强化学习 DQN 玩转 gym Mountain Car TensorFlow 2.0 (七) - 强化学习 Q-Learning 玩转 OpenAI gym 博客折腾记(三) - 主题设计、彩蛋与阅读量翻倍 | 极客兔兔 TensorFlow 2.0 (六) - 监督学习玩转 OpenAI gym game 博客折腾记(二) - 对搜索引擎的理解 | 极客兔兔 博客折腾记(一) - 极致性能的尝试 | 极客兔兔 Pandas 数据处理(三) - Cheat Sheet 中文版 TensorFlow 2.0 (五) - mnist手写数字识别(CNN卷积神经网络) TensorFlow入门(四) - mnist手写数字识别(制作h5py训练集) | 极客兔兔 TensorFlow入门(三) - mnist手写数字识别(可视化训练) | 极客兔兔 Pandas 数据处理(二) - 筛选数据 | 极客兔兔 Pandas 数据处理(一) - DataFrame 与 Series
Go 死码消除与调试(debug)模式 | Go 语言高性能编程
2021-01-11 · via 极客兔兔

源代码/数据集已上传到 Github - high-performance-go

golang compiler optimization

1 什么是死码消除

以下摘自内容 Dead code elimination - wikipedia

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.

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

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.

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

2 Go 语言中的应用

2.1 使用常量提升性能

在某些场景下,将变量替换为常量,性能会有很大的提升。

举一个简单的例子,以下是 maxvar.go 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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)
}
}
  • max 是一个非常简单的函数,返回两个值中的较大值。
  • a 和 b 是两个全局变量,赋值为 10 和 20。
  • 如果 a 大于 b,那么将会调用 time.Sleep() 休眠 3 秒。

拷贝 maxvar.gomaxconst.go,并将 var a, b 修改为 const a, b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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)
}
}

编译 maxvar.gomaxconst.go,并比较编译后的二进制大小:

1
2
3
4
5
go build -o maxvar maxvar.go
go build -o maxconst maxconst.go
ls -l maxvar maxconst
-rwxr-xr-x 1 x x 1895424 Jan 10 00:01 maxconst
-rwxr-xr-x 1 x x 2120368 Jan 10 00:01 maxvar

我们可以看到 maxconstmaxvar 体积小了约 10% = 0.22 MB。

为什么会出现 11% 的差异呢?

我们使用 -gcflags=-m 参数看一下编译器做了哪些优化:

1
2
3
4
go build -gcflags=-m  -o maxvar maxvar.go

./maxconst.go:7:6: can inline max
./maxconst.go:17:8: inlining call to max

max 函数被内联了,即被展开了,手动展开后如下:

1
2
3
4
5
6
7
8
9
10
11
func main() {
var result int
if a > b {
result = a
} else {
result = b
}
if result == a {
fmt.Println(a)
}
}

那如果 a 和 b 均为常量(const)呢?那在编译阶段就可以直接进行计算:

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)
}
}

计算之后,10 > 20 永远为假,那么分支消除后:

1
2
3
4
5
func main() {
if 20 == 10 {
fmt.Println(a)
}
}

进一步,20 == 10 也永远为假,再次分支消除:

1
func main() {}

但是如果全局变量 a、b 不为常量,即 maxvar 中声明的一样,编译器并不知道运行过程中 a、b 会不会发生改变,因此不能够进行死码消除,这部分代码被编译到最终的二进制程序中。因此 maxvarmaxconst 二进制体积大了约 10%。

如果在 if 语句中,调用了更多的库,死码消除之后,体积差距会更大。

因此,在声明全局变量时,如果能够确定为常量,尽量使用 const 而非 var,这样很多运算在编译器即可执行。死码消除后,既减小了二进制的体积,又可以提高运行时的效率,如果这部分代码是 hot path,那么对性能的提升会更加明显。

2.2 可推断的局部变量

考虑另一种情况,a、b 作为局部变量呢?

1
2
3
4
5
6
7

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

编译结果如下,大小与 varconst 一致,即 a、b 作为局部变量时,编译器死码消除是生效的。

1
2
3
$ go build -o maxvarlocal maxvarlocal.go
$ ls -l maxvarlocal
-rwxr-xr-x 1 x x 1895424 Jan 10 00:05 maxvarlocal

那如果再修改一下,函数中增加修改 a、b 变量的并发操作。

1
2
3
4
5
6
7
8
9
func main() {
var a, b = 10, 20
go func() {
b, a = a, b
}()
if max(a, b) == a {
fmt.Println(a)
}
}

编译结果如下,大小增加了 10%,此时,a、b 的值不能有效推断,死码消除失效。

1
2
3
$ go build -o maxvarlocal maxvarlocal.go
$ ls -l maxvarlocal
-rwxr-xr-x 1 x x 2120352 Jan 10 00:05 maxvarlocal

其实这个结果很好理解,包(package)级别的变量和函数内部的局部变量的推断难度是不一样的。函数内部的局部变量的修改只会发生在该函数中。但是如果是包级别的变量,对该变量的修改可能出现在:

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

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

以上例子,基于 go1.13.6 darwin/amd64

2.3 调试(debug)模式

我们可以在源代码中,定义全局常量 debug,值设置为 false,在需要增加调试代码的地方,使用条件语句 if debug 包裹,例如下面的例子:

1
2
3
4
5
6
7
const debug = false

func main() {
if debug {
log.Println("debug mode is enabled")
}
}

如果是正常编译,常量 debug 始终等于 false,调试语句在编译过程中会被消除,不会影响最终的二进制大小,也不会对运行效率产生任何影响。

那如果我们想编译出 debug 版本的二进制呢?可以将 debug 修改为 true 之后编译。这对于开发者日常调试是非常有帮助的,日常开发过程中,在进行单元测试或者是简单的集成测试时,希望能够执行一些额外的操作,例如打印日志,或者是修改变量的值。提交代码时,再将 debug 修改为 false,开发过程中增加的额外的调试代码在编译时会被消除,不会对正式版本产生任何的影响。

Go 语言源代码中有很多这样的例子:

1
2
3
4
5
6
7
8
$ grep -nr "const debug = false" "$(dirname $(which go))/../src"
/usr/local/go/bin/../src/cmd/go/internal/modfile/read.go:606: const debug = false
/usr/local/go/bin/../src/cmd/compile/internal/syntax/parser.go:14:const debug = false

/usr/local/go/bin/../src/net/http/transport_test.go:2037: const debug = false
/usr/local/go/bin/../src/net/http/transport_test.go:2095: const debug = false
/usr/local/go/bin/../src/go/types/initorder.go:23: const debug = false
/usr/local/go/bin/../src/go/internal/gcimporter/gcimporter.go:22:const debug = false

2.4 条件编译

有没有不修改源代码,也能编译出 debug 版本的方式呢?

答案是肯定的:有,可结合 build tags 来实现条件编译。

新建 release.godebug.go

  • debug.go
1
2
3
4
5


package main

const debug = true
  • release.go
1
2
3
4
5


package main

const debug = false

main.go 中去掉常量 debug 的定义:

1
2
3
4
5
6
7
8
9
package main

import "log"

func main() {
if debug {
log.Println("debug mode is enabled")
}
}
  • // +build debug 表示 build tags 中包含 debug 时,该源文件参与编译。
  • // +build !debug 表示 build tags 中不包含 debug 时,该源文件参与编译。

一个源文件中可以有多个 build tags,同一行的空格隔开的 tag 之间是逻辑或的关系,不同行之间的 tag 是逻辑与的关系。例如下面的写法表示:此源文件只能在 linux/386 或者 darwin/386 平台下编译。

1
2


接下来,我们编译一个 debug 版本并运行:

1
2
3
$ go build -tags debug -o debug .  
$ ./debug
2021/01/11 00:10:40 debug mode is enabled

编译 release 版本并运行:

1
2
3
$ go build -o release .
$ ./release

除了全局布尔值常量 debug 以外,debug.gorelease.go 还可以根据需要添加其他代码。例如,相同的函数定义,debug 和 release 模式下有不同的函数实现。

附 推荐与参考


edit this page last updated at 2026-02-23