(译) Go的工具概述

(译) Go 的工具概述

有时我会被问到 “你为什么喜欢使用 Go?” 我经常提到的一件事是作为 go 命令的一部分与语言一起存在的周到工具。我每天都会使用一些工具(例如 go fmt 和)go build,而其他类似工具 go tool pprof 仅用于帮助解决特定问题。但是在所有情况下,我都很欣赏它们使我的项目管理和维护更加容易的事实。

在这篇文章中,我希望提供一些有关我发现最有用的工具的背景知识和背景知识,并且重要的是,说明它们如何适合典型项目的工作流程。如果你是 Go 的新手,希望它能给你一个良好的开端。

或者,如果你使用 Go 已有一段时间,并且该内容不适用于你,则希望你仍会发现以前不知道的命令或标志:)

这篇文章中的信息是为 Go 1.12 编写的,并假定你正在一个启用了模块的项目中。

  1. 安装工具
  2. 查看环境信息
  3. 发展历程

  4. 测试中

  5. 提交前检查

  6. 构建和部署

  7. 诊断问题并进行优化

  8. 管理依赖关系

  9. 升级到新的 Go 版本
  10. 报告错误
  11. 备忘单

安装工具

在这篇文章中,我将主要关注作为 go 命令一部分的工具。但是我将要提到的一些内容不是标准 Go 1.12 版本的一部分。

要在使用 Go 1.12 时安装这些程序,首先需要确保你_不在_模块启用的目录中(我通常只是更改为 /tmp)。然后,你可以使用 GO111MODULE=on go get 命令安装该工具。例如:

1
2
$ cd /tmp
$ GO111MODULE=on go get golang.org/x/tools/cmd/stress

这将下载相关的软件包和依赖项,生成可执行文件并将其添加到你的 GOBIN 目录中。如果你尚未明确设置 GOBIN 目录,则可执行文件将添加到你的 GOPATH/bin 文件夹中。无论哪种方式,都应确保系统路径上有适当的目录。

注意:此过程有点笨拙,并有望在以后的 Go 版本中改进。问题 30515 正在跟踪有关此问题的讨论。

查看环境信息

你可以使用该 go env 工具显示有关当前 Go 操作环境的信息。如果你在不熟悉的计算机上工作,这可能特别有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/alex/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/alex/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build245740092=/tmp/go-build -gno-record-gcc-switches"

如果你需要特定的值,可以将它们作为参数传递给 go env。例如:

1
2
3
4
$ go env GOPATH GOOS GOARCH
/home/alex/go
linux
amd64

要显示所有 go env 变量和值的文档,可以运行:

1
$ go help environment

发展历程

运行代码

在开发过程中,该 go run 工具是试用代码的便捷方式。从本质上讲,它是一种编译代码,在 /tmp 目录中创建可执行二进制文件然后一步运行此二进制文件的快捷方式。

1
2
$ go run .          # Run the package in the current directory
$ go run ./cmd/foo # Run the package in the ./cmd/foo directory

注意:从 Go 1.11 开始,你可以像上面一样将包路径传递到 go run。这意味着你不再需要使用 go run *.go 通配符扩展之类的变通办法来运行多个文件。我非常喜欢这种改进!

获取依赖项

假设你有启用的模块,当你使用 go run(或 go testgo build 与此有关的)任何外部的依赖关系会自动(和递归)下载到符合 import 你的代码语句。默认情况下,将下载依赖项的最新标记版本,或者如果没有可用的标记版本,则在最新提交时依赖项。

如果你事先知道需要特定版本的依赖项(而不是 Go 默认会获取的 go get 版本),则可以使用相关的版本号或提交哈希。例如:

1
2
$ go get github.com/foo/bar@v1.2.3
$ go get github.com/foo/bar@8e1b8d3

如果相关性被提取有 go.mod 文件,那么它的依赖不会列出_你的_ go.mod 文件。相反,如果要下载的依赖项没有 go.mod 文件,则该依赖项_将_在 go.mod 文件中列出,并在其 // indirect 旁边带有注释。

因此,这意味着你的 go.mod 文件不一定会在一处显示项目的所有依赖关系。相反,你可以使用以下 go list 工具查看所有内容:

1
$ go list -m all

有时你可能会问,为什么是一个依赖?你可以使用 go mod why 命令回答此问题,该命令将显示从主模块中的软件包到给定依赖项的最短路径。例如:

1
2
3
4
5
$ go mod why -m golang.org/x/sys
# golang.org/x/sys
github.com/alexedwards/argon2id
golang.org/x/crypto/argon2
golang.org/x/sys/cpu

注意:该 go mod why 命令将为大多数(但不是全部)依赖项返回答案。问题 27900 正在对此进行跟踪。

如果你有兴趣分析或可视化应用程序的依赖关系,那么你可能还需要签出该 go mod graph 工具。还有用于生成可视化一个伟大的教程和示例代码在这里

最后,下载的依赖项存储在位于的模块缓存GOPATH/pkg/mod。如果你需要清除模块缓存,则可以使用该 go clean 工具。但是请注意:这将删除计算机上所有项目的下载依赖关系。

1
$ go clean -modcache

重构代码

你可能对使用该 gofmt 工具自动设置代码格式很熟悉。但它也支持重写规则,可用于帮助重构代码。我会示范。

假设你具有以下代码,并且想要将 foo 变量更改为 Foo 以便将其导出。

1
2
3
4
5
6
var foo int

func bar() {
foo = 1
fmt.Println("foo")
}

为此,你可以 gofmt-r 标志一起使用以实现重写规则,-d 标志可以显示更改的差异,-w 标志可以在适当的位置进行更改,如下所示:

1
2
3
4
5
6
7
8
9
$ gofmt -d -w -r 'foo -> Foo' .
-var foo int
+var Foo int

func bar() {
- foo = 1
+ Foo = 1
fmt.Println("foo")
}

注意,这比查找和替换聪明吗?该 foo 变量已发生变化,但 "foo" 在串 fmt.Println() 语句已保持不变。需要注意的另一件事是该 gofmt 命令是递归工作的,因此上述命令将 *.go 在当前目录和子目录中的所有文件上运行。

如果要使用此功能,我建议运行重写规则,而不-w 标志第一次,第一次检查 diff 来确保该修改的代码是你期望的。

让我们看一个稍微复杂的例子。假设你要更新代码以使用新的 Go 1.12 strings.ReplaceAll()函数而不是 strings.Replace()。要进行此更改,你可以运行:

1
$ gofmt -w -r 'strings.Replace(a, b, c, -1) -> strings.ReplaceAll(a, b, c)' .

在重写规则中,单个小写字符充当与任意表达式匹配的通配符,并且这些表达式将在替换中被替换。

查看 Go 文档

你可以使用 go doc 工具通过终端查看标准库软件包的文档。在开发过程中,我经常使用它来快速检查某些内容,例如特定功能的名称或签名。我发现它比浏览基于 Web 的文档更快,并且它始终也可以脱机使用。

1
2
3
4
5
$ go doc strings            # View simplified documentation for the strings package
$ go doc -all strings # View full documentation for the strings package
$ go doc strings.Replace # View documentation for the strings.Replace function
$ go doc sql.DB # View documentation for the database/sql.DB type
$ go doc sql.DB.Query # View documentation for the database/sql.DB.Query method

你还可以包括该 -src 标志以显示相关的 Go 源代码。例如:

1
$ go doc -src strings.Replace   # View the source code for the strings.Replace function

测试中

运行测试

你可以使用该 go test 工具在项目中运行测试,如下所示:

1
2
3
$ go test .          # Run all tests in the current directory
$ go test ./... # Run all tests in the current directory and sub-directories
$ go test ./foo/bar # Run all tests in the ./foo/bar directory

通常,我会在启用 Go 的竞争检测器的情况下运行测试,这可以帮助拾取现实使用中可能发生的_一些_数据竞争。像这样:

1
go test -race ./...

重要的是要注意,启用竞赛检测器会增加测试的总体运行时间。因此,如果你经常在 TDD 工作流程中运行测试,则可能更愿意将其保存为仅用于预提交测试运行。

从 1.10 开始,Go 在程序包级别缓存测试结果。如果程序包在两次测试运行之间没有变化(并且你使用的是可 go test 缓存的相同标志),则将显示缓存的测试结果并在其 "(cached)" 旁边显示。这对于加快大型代码库的测试运行时间非常有帮助。如果要强制测试完全运行(并避免缓存),则可以使用该 -count=1 标志,也可以使用该 go clean 工具清除所有缓存的测试结果。

1
2
$ go test -count=1 ./...    # Bypass the test cache when running tests
$ go clean -testcache # Delete all cached test results

注意:缓存的测试结果与缓存的构建结果一起存储在你的 GOCACHE 目录中。检查 go env GOCACHE 你不确定机器上的位置。

你可以 go test 通过使用 -run 标志来限制运行特定的测试(和子测试)。这将接受一个正则表达式,并且仅运行名称与该正则表达式匹配的测试。我喜欢将其与 -v 标志结合使用以启用详细模式,因此将显示正在运行的测试和子测试的名称。这是一种有用的方法,可以确保我没有搞砸正则表达式,并且确实可以运行我期望的测试!

1
2
3
 $ go test -v -run=^TestFooBar$ .          # Run the test with the exact name TestFooBar
$ go test -v -run=^TestFoo . # Run tests whose names start with TestFoo
$ go test -v -run=^TestFooBar$/^Baz$ . # Run the Baz subtest of the TestFooBar test only

最好注意几个标记 -short(可以用来跳过长时间运行的测试)和 -failfast(在第一次失败后将停止运行进一步的测试)。请注意,这 -failfast 将防止缓存测试结果。

1
2
$ go test -short ./...      # Skip long running tests
$ go test -failfast ./... # Don't run further tests after a failure.

分析测试覆盖率

你可以使用该 -cover 标志在运行测试时启用覆盖率分析。这将显示每个包的输出中测试覆盖的代码百分比,类似于以下内容:

1
2
$ go test -cover ./...
ok github.com/alexedwards/argon2id 0.467s coverage: 78.6% of statements

你还可以使用标志生成 coverage 配置文件-coverprofile 并使用以下 go tool cover -html 命令在 Web 浏览器中查看它:

1
2
$ go test -coverprofile=/tmp/profile.out ./...
$ go tool cover -html=/tmp/profile.out`

这将为你提供所有测试文件的可导航列表,测试覆盖的代码以绿色显示,未覆盖的代码以红色显示。

如果需要,可以更进一步,设置 -covermode=count 标志以使 coverage 配置文件记录测试期间每个语句执行的确切_次数_。

1
2
$ go test -covermode=count -coverprofile=/tmp/profile.out ./...
$ go tool cover -html=/tmp/profile.out

在浏览器中查看时,执行频率更高的语句以更加饱和的绿色阴影显示,类似于:

注意:如果 t.Parallel() 在任何测试中都使用该命令,则应使用该标志 -covermode=atomic 代替,-covermode=count 以确保计数准确。

最后,如果你没有可用于查看覆盖范围配置文件的网络浏览器,则可以使用以下命令在终端中按功能 / 方法查看测试覆盖率的细分:

1
2
3
4
$ go tool cover -func=/tmp/profile.out
github.com/alexedwards/argon2id/argon2id.go:77: CreateHash 87.5%
github.com/alexedwards/argon2id/argon2id.go:96: ComparePasswordAndHash 85.7%
...

压力测试

你可以使用该 go test -count 命令连续运行测试多次,如果你要检查偶发性或间歇性故障,这将很有用。例如:

1
go test -run=^TestFooBar$ -count=500 .

在此示例中,TestFooBar 测试将连续重复 500 次。但重要的是要注意,测试将连续串行进行 - 即使它包含 t.Parallel() 指令。因此,如果你的测试运行相对较慢,例如往返数据库,硬盘或 Internet,则运行大量测试可能会花费很长时间。

在这种情况下,你可能希望使用该 stress工具并行并行重复多次相同的测试。你可以这样安装它:

1
2
$ cd /tmp
$ GO111MODULE=on go get golang.org/x/tools/cmd/stress

要使用该 stress 工具,你首先需要为要_测试的特定程序包编译一个测试二进制文件_。你可以使用 go test -c 命令来完成。例如,要在当前目录中为程序包创建一个测试二进制文件:

1
go test -c -o=/tmp/foo.test .

在此示例中,测试二进制文件将输出到 /tmp/foo.test。然后,你可以使用该 stress 工具在测试二进制文件中执行特定的测试,如下所示:

1
2
3
4
$ stress -p=4 /tmp/foo.test -test.run=^TestFooBar$
60 runs so far, 0 failures
120 runs so far, 0 failures
...

注意:在上面的示例中,我使用了 -p 标志将所使用的并行进程数限制 stress 为 4。没有该标志,该工具将默认使用等于的进程数 runtime.NumCPU()

测试所有依赖项

在构建用于发布或部署的可执行文件或公开分发代码之前,可能需要运行以下 go test all 命令:

1
$ go test all

这将对模块中的所有软件包以及所有依赖项(包括测试测试依赖项和必要的标准库软件包)运行测试,并且可以帮助验证所使用的依赖项的确切版本是否相互兼容。这可能需要很长时间才能运行,但是结果缓存良好,因此以后的任何后续测试都应该更快。如果需要,还可以使用 go test -short all 跳过任何长时间运行的测试。

提交前检查

格式化代码

Go 提供了两种工具,可以根据 Go 约定自动设置代码格式:gofmtgo fmt。使用这些有助于使代码在文件和项目之间保持一致,并且 - 如果在提交代码之前使用它们 - 有助于减少检查文件版本之间的差异时的噪音。

我喜欢将 gofmt 工具与以下标志一起使用:

1
2
$ gofmt -w -s -d foo.go  # Format the foo.go file
$ gofmt -w -s -d . # Recursively format all files in the current directory and sub-directories

在这些命令中,该 -w 标志指示该工具在适当的位置重写文件,-s 指示该工具在可能的情况下对代码进行简化,该 -d 标志指示该工具输出更改的差异(因为我很想知道是什么)更改)。如果你只想显示已更改文件的名称,而不是差异,则可以将其交换为 -l 标志。

注意:该 gofmt 命令以递归方式工作。如果你将目录传递给,则它会格式化../cmd/foo 格式化.go 该目录下的所有文件。

另一个格式化工具 - go fmt 工具是包装器,本质上调用 gofmt -l -w 指定的文件或目录。你可以像这样使用它:

1
$ go fmt ./...

执行静态分析

go vet 工具_会对你的代码进行静态分析,并警告你某些可能_与你的代码有误,但编译器不会处理的问题。诸如无法访问的代码,不必要的分配和格式错误的构建标签之类的问题。你可以这样使用它:

1
2
3
4
$ go vet foo.go     # Vet the foo.go file
$ go vet . # Vet all files in the current directory
$ go vet ./... # Vet all files in the current directory and sub-directories
$ go vet ./foo/bar # Vet all files in the ./foo/bar directory

在幕后,go vet 运行一堆不同的分析仪,这些分析仪在此处列出,你可以根据情况禁用特定的分析仪。例如,要禁用 composite 分析仪,可以使用:

1
$ go vet -composites=false ./...

有一对夫妇的实验分析仪 golang.org/x/tools,你可能会想尝试:nilness(这对于冗余或不可能为零比较检查)和阴影(其检查的变数可能出现的意外阴影)。如果要使用它们,则需要单独安装和运行它们。例如,要安装,nilness 请运行:

1
2
$ cd /tmp
$ GO111MODULE=on go get golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness

然后你可以像这样使用它:

1
$ go vet -vettool=$(which nilness) ./...

注意:使用该 -vettool 标志时,它将_仅_运行指定的分析器 - 其他所有 go vet 分析器将不会运行。

附带一提,自 Go 1.10 起,该 go test 工具会 go vet 在运行任何测试之前自动执行小的,高可信度的部分检查。你可以在运行如下测试时关闭此行为:

1
$ go test -vet=off ./...

整理代码

你可以使用该 golint 工具来识别代码中的样式错误。与 go vet 有所不同,这与代码的正确性无关,但是可以帮助你使代码与 Effective Go 和 Go CodeReviewComments 中的样式约定对齐。

它不是标准库的一部分,因此你需要像这样安装它:

1
2
$ cd /tmp
$ GO111MODULE=on go get golang.org/x/lint/golint

然后可以按以下方式运行它:

1
2
3
4
$ golint foo.go     # Lint the foo.go file
$ golint . # Lint all files in the current directory
$ golint ./... # Lint all files in the current directory and sub-directories
$ golint ./foo/bar # Lint all files in the ./foo/bar directory

整理和验证依赖项

在提交对代码的任何更改之前,我建议运行以下两个命令来整理和验证依赖关系:

1
2
$ go mod tidy
$ go mod verify

go mod tidy 命令将修剪任何未使用的依赖从你 go.modgo.sum 文件,并更新文件,包括所有可能的构建标签 / OS / 建筑组合的依赖关系(注:go rungo testgo build 等都是 “懒惰”,将只取所需当前构建标签包 / OS / 体系结构)。在每次提交之前运行此命令,可以更轻松地确定在查看版本控制历史记录时哪些代码更改负责添加或删除哪些依赖项。

我还建议你使用 go mod verify 命令检查自下载后对计算机的依赖项是否有意外(或有意)更改,并且它们与 go.sum 文件中的加密哈希值匹配。运行此命令有助于确保所使用的依赖项与你期望的依赖项完全相同,并且该提交的任何构建都可以在以后重现。

构建和部署

建立可执行文件

要编译 main 软件包并创建可执行二进制文件,可以使用该 go build 工具。通常,我将其与 -o 标志结合使用,让你显式设置输出目录和二进制文件名称,如下所示:

1
2
$ go build -o=/tmp/foo .            # Compile the package in the current directory
$ go build -o=/tmp/foo ./cmd/foo # Compile the package in the ./cmd/foo directory

在这些示例中,go build 将_编译指定的程序包(和所有相关程序包),然后调用链接器_以生成可执行二进制文件,并将其输出到 /tmp/foo

重要的是要注意,从 Go 1.10 开始,该 go build 工具将构建输出缓存在_构建缓存中_。此缓存的输出将在以后的适当版本中再次重用,这可以显着缩短总体生成时间。这种新的缓存行为方式的古训的 “宁可 go installgo build 以提高缓存” 不再适用。

如果不确定构建缓存在哪里,可以通过运行以下 go env GOCACHE 命令进行检查:

1
2
$ go env GOCACHE
/home/alex/.cache/go-build

使用构建缓存有一个重要的警告 - 它不会检测到使用导入的 C 库的更改 cgo。因此,如果你的代码通过导入了 C 库,cgo 并且自上次构建以来已对其进行了更改,则需要使用 -a 标志来强制重新构建所有软件包。另外,你可以使用 go clean 清除缓存:

1
2
$ go build -a -o=/tmp/foo .     # Force all packages to be rebuilt
$ go clean -cache # Remove everything from the build cache

注意:运行 go clean -cache 也会删除缓存的测试结果。

如果你对 go build 幕后操作感兴趣,则可以使用以下命令:

1
2
$ go list -deps . | sort -u     # List all packages that are used to build the executable
$ go build -a -x -o=/tmp/foo . # Rebuild everything and show the commands that are run

最后,如果你 go build 在非 main 软件包上运行,它将在一个临时位置进行编译,然后再次将结果存储在构建缓存中。没有可执行文件。

交叉编译

这是我最喜欢的 Go 功能之一。

默认情况下,go build 将输出适合在你当前的操作系统和体系结构上使用的二进制文件。但是它也支持交叉编译,因此你可以生成适合在另一台计算机上使用的二进制文件。如果你要在一个操作系统上进行开发,而在另一个操作系统上进行部署,这将特别有用。

你可以通过分别设置 GOOSGOARCH 环境变量来指定要为其创建二进制文件的操作系统和体系结构。例如:

1
2
$ GOOS=linux GOARCH=amd64 go build -o=/tmp/linux_amd64/foo .
$ GOOS=windows GOARCH=amd64 go build -o=/tmp/windows_amd64/foo.exe .

要查看所有受支持的操作系统 / 体系结构组合的列表,可以运行 go tool dist list

1
2
3
4
5
6
7
8
9
$ go tool dist list
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/386
darwin/amd64
...

提示:你可以使用 Go 的交叉编译来创建 WebAssembly 二进制文件

有关交叉编译的更深入的信息,我建议阅读这篇出色的文章

使用编译器和链接器标志

在生成可执行文件时,你可以使用该 -gcflags 标志来更改编译器的行为,并查看有关其工作方式的更多信息。你可以通过运行以下命令查看可用编译器标志的完整列表:

1
$ go tool compile -help

你可能会发现一个有趣的标志 -m,它会触发打印有关在编译过程中做出的优化决策的信息。你可以像这样使用它:

1
$ go build -gcflags="-m -m" -o=/tmp/foo . # Print information about optimization decisions

在上面的示例中,我 -m 两次使用该标志来指示我想将决策信息打印为两层。你只需使用一个就可以得到更简单的输出。

另外,从 Go 1.10 开始,编译器标志仅适用于传递给的特定程序包 go build- 在上例中,该程序包是当前目录中的程序包(由表示.)。如果要打印所有程序包(包括依赖项)的优化决策,可以改用以下命令:

1
$ go build -gcflags="all=-m" -o=/tmp/foo .

从 Go 1.11 开始,你应该发现调试优化的二进制文件比以前更容易。但是,如果需要,你仍然可以使用这些标志 -N 来禁用优化和 -l 禁用内联。例如:

1
$ go build -gcflags="all=-N -l" -o=/tmp/foo .  # Disable optimizations and inlining

你可以通过运行以下命令查看可用的链接器标志列表:

1
$ go tool link -help

其中最著名的可能是 -X 标志,它允许你将(字符串)值 “烧入” 应用程序中的特定变量。通常用于添加版本号或提交哈希。例如:

1
$ go build -ldflags="-X main.version=1.2.3" -o=/tmp/foo .

有关该 -X 标志的更多信息和一些示例代码,请参见此 StackOverflow 问题以及本帖子和本帖子

你可能还对使用 -s-w 标志从二进制文件中删除调试信息感兴趣。这通常会将最终尺寸减少约 25%。例如:

1
$ go build -ldflags="-s -w" -o=/tmp/foo .  # Strip debug information from the binary

注意:如果你需要优化二进制大小,则可能需要使用 upx 对其进行压缩。有关更多信息,请参见此帖子

诊断问题并进行优化

运行和比较基准

Go 的一个不错的功能是,它使基准测试变得容易。如果你不熟悉编写基准测试的一般过程,这里这里都有很好的指南。

要运行基准测试,你将需要使用该 go test 工具,并将 -bench 标志设置为与要执行的基准匹配的正则表达式。例如:

1
2
3
$ go test -bench=. ./...                        # Run all benchmarks and tests
$ go test -run=^$ -bench=. ./... # Run all benchmarks (and no tests)
$ go test -run=^$ -bench=^BenchmarkFoo$ ./... # Run only the BenchmarkFoo benchmark (and no tests)

我几乎总是使用该 -benchmem 标志运行基准测试,该标志会强制将内存分配统计信息包含在输出中。

1
$ go test -bench=. -benchmem ./...

默认情况下,每一个基准测试将为运行_最少_ 1 次,只有一次的。你可以使用 -benchtime-count 标志进行更改:

1
2
3
$ go test -bench=. -benchtime=5s ./...       # Run each benchmark test for at least 5 seconds
$ go test -bench=. -benchtime=500x ./... # Run each benchmark test for exactly 500 iterations
$ go test -bench=. -count=3 ./... # Repeat each benchmark test 3 times over

如果基准测试的代码使用并发性,则可以使用该 -cpu 标志来查看更改 GOMAXPROCS 值(实际上是可以同时执行 G​​o 代码的 OS 线程数)对性能的影响。例如,运行 GOMAXPROCS 设置为 1、4 和 8 的基准测试:

1
$ go test -bench=. -cpu=1,4,8 ./...

要比较基准之间的更改,你可能需要使用 Benchcmp 工具。这不是标准 go 命令的一部分,因此你需要像这样安装它:

1
2
$ cd /tmp
$ GO111MODULE=on go get golang.org/x/tools/cmd/benchcmp

然后可以像这样使用它:

1
2
3
4
5
6
7
8
9
10
11
12
$ go test -run=^$ -bench=. -benchmem ./... > /tmp/old.txt
# make changes
$ go test -run=^$ -bench=. -benchmem ./... > /tmp/new.txt
$ benchcmp /tmp/old.txt /tmp/new.txt
benchmark old ns/op new ns/op delta
BenchmarkExample-8 21234 5510 -74.05%

benchmark old allocs new allocs delta
BenchmarkExample-8 17 11 -35.29%

benchmark old bytes new bytes delta
BenchmarkExample-8 8240 3808 -53.79%

分析和跟踪

Go 使创建用于 CPU 使用,内存使用,goroutine 阻止和互斥锁争用的诊断配置文件成为可能。你可以使用它们进行更深入的了解,并确切地了解你的应用程序如何使用(或等待)资源。

有三种方法可以生成配置文件:

  • 如果你有 Web 应用程序,则可以导入 net/http/pprof软件包。这将向中注册一些处理程序,http.DefaultServeMux 然后你可以使用这些处理程序为正在运行的应用程序生成和下载配置文件。这篇文章提供了很好的解释和一些示例代码。
  • 对于其他类型的应用程序,你可以使用 pprof.StartCPUProfile()pprof.WriteHeapProfile() 函数来分析正在运行的应用程序。请参阅 runtime/pprof文档以获取示例代码。
  • 或者,你可以在运行基准测试或测试时通过使用各种 -***profile 标志来生成概要文件,如下所示:
1
2
3
4
$ go test -run=^$ -bench=^BenchmarkFoo$ -cpuprofile=/tmp/cpuprofile.out .
$ go test -run=^$ -bench=^BenchmarkFoo$ -memprofile=/tmp/memprofile.out .
$ go test -run=^$ -bench=^BenchmarkFoo$ -blockprofile=/tmp/blockprofile.out .
$ go test -run=^$ -bench=^BenchmarkFoo$ -mutexprofile=/tmp/mutexprofile.out .

注意:-***profile 在运行基准测试或测试时使用这些标志将导致测试二进制文件输出到你的当前目录。如果要将其输出到其他位置,则应使用如下 -o 标记:

1
$ go test -run=^$ -bench=^BenchmarkFoo$ -o=/tmp/foo.test -cpuprofile=/tmp/cpuprofile.out .

无论选择哪种方式创建配置文件,启用概要分析后,你的 Go 程序都将每秒停止约 100 次,并在该时刻进行快照。将这些_样本收集在一起以形成一个配置文件_,你可以使用该 pprof 工具进行分析。

我最喜欢的检查配置文件的方法是使用 go tool pprof -http 命令在 Web 浏览器中将其打开。例如:

1
$ go tool pprof -http=:5000 /tmp/cpuprofile.out

这将默认为显示一个_图形_,该_图形_显示了应用程序采样方面的执行树,这使你可以快速了解任何资源使用 “热点”。在上图中,我们可以看到,就 CPU 使用率而言,热点是源自的两个系统调用 ioutil.ReadFile()

你还可以导航到配置文件的其他_视图_,包括按功能和源代码列出的最高使用率。

如果信息量不胜枚举,则你可能希望使用该 --nodefraction 标志来忽略占少于一定百分比样本的节点。例如,要忽略少于 10%的样本中使用的节点,可以这样运行 pprof

1
$ go tool pprof --nodefraction=0.1 -http=:5000 /tmp/cpuprofile.out

这使图形的 “噪点” 少了很多,如果放大此屏幕截图,现在可以更加清楚地看到并了解 CPU 使用率热点所在的位置。

分析和优化资源使用情况是一个很大,细微的话题,而我在这里几乎没有涉及到任何表面。如果你有兴趣了解更多信息,那么我建议你阅读以下博客文章:

可以用来帮助诊断问题的另一个工具是运行时执行跟踪程序。这使你可以了解 Go 如何创建和调度 goroutine,以及何时运行垃圾回收器,以及有关阻止 syscall /network/sync 操作的信息。

同样,你可以从测试或基准测试中生成跟踪,或者用于 net/http/pprof 创建和下载 Web 应用程序的跟踪。然后,你可以使用 go tool trace 来在 Web 浏览器中查看输出,如下所示:

1
2
$ go test -run=^$ -bench=^BenchmarkFoo$ -trace=/tmp/trace.out .
$ go tool trace /tmp/trace.out

重要提示:目前仅在 Chrome / Chromium 中可见。

有关 Go 的执行跟踪器以及如何解释输出的更多信息,请参见 Rhys Hiltner 的 dotGo 2016 演讲和这篇出色的博客文章

检查比赛条件

我之前谈到过在测试过程中使用启用 Go 的竞态检测器 go test -race。但是你也可以在构建可执行文件时启用它以运行程序,如下所示:

1
$ go build -race -o=/tmp/foo .

需要特别注意的是,启用了竞争检测器的二进制文件将比 -race 正常情况下使用更多的 CPU 和内存,因此,在正常情况下为生产构建二进制文件时,不应使用该标志。

但是你可能希望在多个池中的一台服务器上部署启用了竞争检测器的二进制文件。或通过使用负载测试工具将流量并发到启用了种族检测器的二进制文件上,同时使用它来帮助查找可疑的竞争状况。

默认情况下,如果在二进制文件运行时检测到任何竞争,则会将日志写入 stderr。你可以 GORACE 根据需要使用环境变量来更改它。例如,要运行位于的二进制文件 /tmp/foo 并向其输出任何种族日志,/tmp/race.<pid> 可以使用:

1
$ GORACE="log_path=/tmp/race" /tmp/foo

管理依赖关系

你可以使用该 go list 工具检查特定依赖项是否具有可用的较新版本,如下所示:

1
2
$ go list -m -u github.com/alecthomas/chroma
github.com/alecthomas/chroma v0.6.2 [v0.6.3]

这将输出你当前正在使用的依赖项名称和版本 [],如果存在较新的版本,则会在方括号中输出最新版本。你还可以 go list 像这样检查所有依赖项(和子依赖项)的更新:

1
$ go list -m -u all

你可以使用以下 go get 命令将依赖项升级(或降级)为最新版本,特定的标记发布或提交哈希:

1
2
3
$ go get github.com/foo/bar@latest
$ go get github.com/foo/bar@v1.2.3
$ go get github.com/foo/bar@7e0369f

如果要更新的依赖项有一个 go.mod 文件,则根据此 go.mod 文件中的信息,如有必要,还将下载对任何子依赖项的更新。如果使用该 go get -u 标志,则 go.mod 文件的内容将被忽略,所有子依赖项都将升级到其最新的次要版本 / 修补程序版本,即使 go.mod 指定了其他版本也是如此。

升级或降级任何依赖项后,最好整理一下 modfile。你可能还想对所有软件包运行测试以帮助检查不兼容性。像这样:

1
2
$ go mod tidy
$ go test all

有时,你可能希望使用依赖项的本地版本(例如,在补丁合并到上游之前,你需要使用本地派生)。为此,可以使用 go mod edit 命令将 go.mod 文件中的依赖项替换为本地版本。例如:

1
$ go mod edit -replace=github.com/alexedwards/argon2id=/home/alex/code/argon2id

这将添加一个替换规则,以你的 go.mod 文件像这样,和任何未来的调用 go rungo build 等将使用本地版本。

档案:go.mod

1
2
3
4
5
6
7
module alexedwards.net/example

go 1.12

require github.com/alexedwards/argon2id v0.0.0-20190109181859-24206601af6c

replace github.com/alexedwards/argon2id => /home/alex/Projects/playground/argon2id

一旦不再需要,你可以使用以下命令删除替换规则:

1
$ go mod edit -dropreplace=github.com/alexedwards/argon2id

你可以使用相同的通用技术来导入_仅存_在于你自己的文件系统上的软件包。如果你同时在开发中处理多个模块,而其中一个依赖于另一个模块,则这很有用。

注意:如果你不想使用该 go mod edit 命令,则可以 go.mod 手动编辑文件以进行这些更改。无论哪种方式都可以。

升级到新的 Go 版本

go fix 工具最初于 2011 年发布(当时仍在对 Go’s API 进行定期更改),以帮助用户自动更新其旧代码以与 Go 的最新版本兼容。从那时起,Go 的兼容性承诺意味着,如果你从一个 Go 1.x 版本升级到一个新的 1.x 版本,则一切正常工作和使用 go fix 通常都是不必要的。

但是,它确实处理了一些非常具体的问题。你可以通过运行来查看它们的摘要 go tool fix -help。如果你决定 go fix 升级后要运行,则应运行以下命令,然后在提交更改之前先检查一下差异。

1
$ go fix ./...

报告错误

如果你确信已发现 Go 的标准库,工具或文档中未报告的问题,则可以使用该 go bug 命令来创建新的 Github 问题。

1
go bug

这将打开一个浏览器窗口,其中包含一个预填有你的系统信息和报告模板的问题。

备忘单

更新 2019-04-19:@FedirFR 已经根据这篇文章做了一个备忘单。你可以在这里下载

如果你喜欢这篇博客文章,请不要忘记阅读有关如何使用 Go 构建专​​业 Web 应用程序的新书!

在 Twitter @ajmedwards 上关注我。

根据 MIT 许可证,本文中的所有代码段均可免费使用。

评论

0 comments
Anonymous
Markdown is supported

Be the first person to leave a comment!

`
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×