logo

罕见的 Go CLI 命令 (3/5):如何修改运行时环境

Go 不仅提供了对构建环境的细粒度控制,也提供了对运行时环境的多维度操作,本文从六个环境变量全面而浅显地看一下 Go 是如何允许我们修改运行时环境的,目的是对这些概念有个大致的了解,当真正遇到相关问题时也可以在此基础上深入探究。

1. GOMAXPROCS

GOMAXPROCS 应该比较众所周知,它可以用来控制 Go 程序中同时执行的操作系统线程数量。它的值最大是 CPU 的核数,设置超过核数的值效果等于核数,即对于一个八核的 CPU 来说,设置 GOMAXPROCS 为 100 或 8 效果是一样的。

例如 这段代码 以并发的方式计算斐波那契数列中前 1000 个数字的哈希值,并将这些哈希值相加得到最终结果。程序的目的是让 CPU 密集执行一段时间,方便我们测试。出于测试的目的,哈希结果被注释掉,仅保留程序运行时间。

通过给程序提供不同的 GOMAXPROCS 值,可以看到并发程序可以显著提高效率。

GOMAXPROCS=1 go run 01-gomaxprocs.go # Elapsed time: 1.999676148s

GOMAXPROCS=100 go run 01-gomaxprocs.go # Elapsed time: 1.303461931s

go run 01-gomaxprocs.go # Elapsed time: 1.367937211s

后两个命令的运行效果其实是一样的,因为 GOMAXPROCS 的默认值就是 CPU 的核数。可以看到充分利用并发使本程序的运行效率提高了 32% 左右。

本程序先计算每个斐波那契数的哈希值,然后再把这 1000 个哈希值相加;哈希值的相加操作并不是简单的字符串拼接或相加,而是对二进制数据进行累加。即使 sumHashCh 中收到的哈希值的顺序并不相同,但对这些二进制数据进行累加操作时,实际上是对二进制数据的位进行逐位相加,因此无论哈希值的顺序如何变化,最终的累加结果都是相同的。

2. GOTRACEBACK

GOTRACEBACK 用于控制 Go 程序在发生崩溃时输出堆栈跟踪的详细程度,帮助调试和定位问题。一般情况下,保持默认就挺好的。

这里的示例代码 和 GOMAXPROCS 的代码一样,只是在第 18 行取消了对 fibCh 的初始化,分别使用不同的跟踪变量值试一下效果:

GOTRACEBACK=none go run 02-gotraceback.go
# panic: runtime error: index out of range [0] with length 0
# exit status 2

go run 02-gotraceback.go
# panic: runtime error: index out of range [0] with length 0

# goroutine 7 [running]:
# main.fib(...)
#   /Users/gaiheilukamei/github/notecodes/01-go-cli/03-runtime/02-gotraceback.go:46
# created by main.main in goroutine 1
#   /Users/gaiheilukamei/github/notecodes/01-go-cli/03-runtime/02-gotraceback.go:34 +0x12f
# exit status 2

上面代码的注释部分即运行程序产生的报错输出。GOTRACEBACK 不同的值分别表示的意义如下:

另外,出于历史原因,可以分别使用 012 代替 noneallsystem

如果在自己的终端尝试一下示例代码,可以看到默认值毫无疑问是最好的,即提供了足够的信息,又不会太芜杂。相比之下,system 提供了令人自卑的海量输出。

3. GOMEMLIMIT

GOMEMLIMIT 应该是一个相当顾名思义的环境变量:限制程序内存使用,避免内存过度消耗。这个设置运行以字节为单位设置内存限制,并支持多种单位后缀,如 B,KiB,MiB 等。

以不同的内存运行示例代码,可以明显感受到程序运行时间的增长。

cd 03-gomemlimit

go run .

GOMEMLIMIT=2MiB go run .

4. GOGC

GOGC 用于设置 Go 程序的垃圾回收目标百分比。当新分配数据与上一次回收后剩余的存活数据的比率达到此百分比时,将触发垃圾回收。默认值为 100,可以通过设置为 off 来完全禁用垃圾回收。调整此值可以影响垃圾回收的触发时机和频率,从而优化程序的内存管理和性能表现。

要操作此值,先来看一下 Go 的默认 GC 是如何工作的,在示例代码目录内分别执行如下命令:

# 首先查看生成一个垃圾回收的追踪文件
go test -trace trace.out .
# 其次,分析生成的文件 (会自动打开默认浏览器)
go tool trace trace.out

# 加快 GC
GOGC=25 go test -trace trace2.out .
go tool trace trace2.out

# 禁用 GC
GOGC=off go test -trace trace3.out .
go tool trace trace3.out

在浏览器中查看 Heap 可以看到,GOGC 的值越低,GC 运行的越频繁。因为在堆内存达到较小的增长百分比时就会启动垃圾回收,所以垃圾回收器将更快地触发。这意味着程序会更频繁地进行内存回收操作,从而减少内存占用。但代价是 CPU 负载增加。

完全禁用 GC 可以提高性能,但代价是堆内存分配增加十倍左右。取舍总是要有的。

5. GORACE

-race 参数可以检查程序中存在的数据竞争,而 GORACE 正是用来控制这种检测器的行为的。通过设置不同的值,可以启用或禁用静态检测器,以及调整检测器的输出和行为。

示例代码进行如下操作:

# 检测是否存在数据竞争
go run -race 05-gorace.go

输出结果的最后一行是:exit status 66,表示程序存在数据竞争,我们可以用下面的方式修改检测器的退出码:

GORACE="exitcode=99" go run -race 05-gorace.go

修改检测器还包括更详细的选项,如 log_pathhalt_on_error 等,可以参考 Data Race Detector #Options

6. GODEBUG

GODEBUG 用于控制 Go 程序中的调试变量,以展示运行时信息。通过设置不同的键值对来启用或禁用特定的调试变量,如内存分配跟踪、CPU 指令集扩展的使用等,以定制化地控制程序的调试输出和行为,满足特定的调试需求。

如直接 cd 06-godebug && go run . 运行示例代码不产生任何输出 (这是故意的,以免程序输出干扰 GODEBUG 输出),使用以下命令则会输出内存分配的信息:

GODEBUG=gctrace=1 go run .
# gc 2 @0.028s 1%: 0.012+0.81+0.021 ms clock, 0.10+0.49/1.0/1.4+0.17 ms cpu, ...

总结

以下三个链接,第一个官方文档包含本文的所有环境变量以及更多,当然解释也更详细;第二个第三个分别代码追踪和静态检测器的文章。