为什么 Go 模块比 GOPATH 更快
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
使用 Go 模块下载依赖项的速度比使用基于 GOPATH 的依赖管理方式要快得多GOPATH。本文中,我们进行了一次全新的依赖项下载实验,GOPATH 模式耗时 9 分 33.845 秒,而模块模式仅需 10.185 秒。本文将解释其中的原因。关键区别在于,模块避免了深度仓库克隆,并且可以使用模块代理。
有关 Go 模块的介绍,请参阅https://blog.golang.org/using-go-modules。
GOPATH基于依赖项的管理会将每个依赖项下载到 中GOPATH/src,在此过程中对版本控制存储库进行深度克隆。
所以,如果你处于GOPATH这种模式,并且你的项目依赖于 A,A 依赖于 B,B 依赖于 C,那么该go命令将对git clone所有三个存储库进行操作(假设它们都使用 git)。
使用 Go 模块时,下载依赖项时需要两样东西:
- 依赖项的源代码。
- 依赖项
go.mod文件。
该go.mod文件用于确定您需要的每个依赖项的版本。go命令收集完go.mod所有依赖项的所有文件后,即可确定您的项目所需的每个依赖项的版本。
例如,如果您直接依赖模块 A 和模块 B,而模块 A 又依赖于模块 B 的 v1.2.0 版本,那么您的模块至少需要依赖于模块B 的 v1.2.0 版本。这个原则同样适用于您直接或间接依赖的每个模块。
附注:请注意,如果模块 A 或 B 发布了新版本,您所需的 B 模块最低版本要求不会改变。该要求长期保持稳定。
模块代理
模块代理是 Go 模块速度如此之快的关键原因之一。模块代理能够理解每个模块依赖项所需的两个组成部分:源代码和文件go.mod。这种理解带来了两项独立的优化:
- 源代码可以以 ZIP 文件的形式分发,而无需进行深度版本控制系统克隆。下载单个 ZIP 文件比进行完整的版本控制系统克隆要快得多,这使得模块下载比 GOPATH 更高效。
- 您可以下载单个
go.mod文件而无需获取其余源代码。这使得使用模块代理比不使用代理更高效。
优化#1意味着当go命令下载依赖项时,需要下载的内容比以前少GOPATH。该命令无需(通常)执行完整的下载git clone,go而是下载一个包含您请求的模块版本源代码的单个zip文件。
如果您使用的模块没有代理,该go命令会尽可能执行浅克隆。浅克隆比深克隆快得多,因为它只检索单个提交,而不是完整的仓库历史记录。
结果是,使用模块时,需要下载的东西比使用其他方式要少得多GOPATH。
优化策略之二,即理解go.mod文件,则更为微妙。
模块出现在模块图中并不意味着它也出现在导入图中。模块图包含go.mod文件中所有模块的列表、所有依赖项的文件、这些依赖项的go.mod所有依赖项的文件等等。导入图包含项目导入的所有包、这些包导入的包等等。你依赖的模块可能包含一些编译项目并不需要的包。go.mod
例如,假设你依赖一个支持 Postgres、MySQL 和 MongoDB 的数据库辅助工具。该辅助工具为每个支持的数据库都提供了一个单独的包,而每个包又依赖于一个第三方模块/包来与该数据库通信。你的项目只使用了辅助工具的 Postgres 包,因此你不需要 MySQL 或 MongoDB 包来构建项目。此外,你也不需要 MySQL 或 MongoDB 模块的源代码来编译项目——它们不在导入图中!
但是,您确实需要go.mod这些模块的文件。如果某个模块位于您的模块图中,则在确定每个依赖项的最低版本时,必须将其考虑在内。
这时模块代理就派上用场了。模块代理可以只提供go.mod给定模块的文件,而无需源代码。如果不使用代理,就必须克隆整个仓库(包括源代码)才能获取该go.mod文件,而这样做会导致代码无法编译。值得注意的是,如上所述,go模块模式下的命令会尽可能执行浅克隆。
在这种模式下GOPATH,go命令只会下载导入图中存在的依赖项。因此,这种特定的代理优化仅比不使用模块代理更能提升性能。延迟模块加载go.mod(计划在 Go 1.16 中推出)将通过减少命令需要获取的文件数量来进一步提升模块速度go。
结果
上述优化意味着(1)您下载的内容更小,(2)需要下载的内容更少。
综合来看,使用代理下载新模块的速度比不使用代理快约 5 倍。相比之下GOPATH,使用代理下载模块的速度甚至可以快 50 倍以上。
理论上,模块可能会比较慢,因为需要单独下载每个依赖项的版本,而不是像依赖项那样在系统中只保留一个版本。但实际上,模块速度更快,而且缓存占用的磁盘空间更少,尤其是在考虑标准的 GOPATH 和 vendoring 模式时。
试试看
要查看这些优化效果,请尝试使用您选择的依赖项运行以下命令。从 Go 1.13 开始,默认的模块代理是proxy.golang.org——如果您使用的是 Go 1.13 或更高版本,并且使用了模块,也没有进行任何调整GOPROXY,那么您已经可以享受到这些优势了。
我使用Cloud Shell运行了这些测试。您的测试结果可能会因您使用的机器、Go 版本、网络速度、测试依赖项以及其他因素而有所不同。
-
首先创建一个临时的、空的
GOPATH测试环境,以免删除原有GOPATH内容。$ mkdir /tmp/tmp.GOPATH $ export GOPATH=/tmp/tmp.GOPATH $ go env GOPATH # Just to confirm. /tmp/tmp.GOPATH -
现在,尝试以下载模式下载依赖项
GOPATH。这些命令使用的是cloud.google.com/go/storage,但您可以尝试使用任何您想要的依赖项:# Force GOPATH mode. Be sure the current directory # doesn't have a go.mod. $ export GO111MODULES=off $ time go get cloud.google.com/go/storage real 9m33.845s user 4m1.197s sys 0m18.079s您可以在 GitHub 上找到cloud.google.com/go/storage
go.mod文件。 -
接下来,尝试使用 Go 模块。创建一个新模块进行测试:
$ mkdir proxy-testing $ cd proxy-testing $ unset GO111MODULES # Back to the default. $ go mod init example.com/proxy-testing -
现在,尝试在不使用代理的情况下下载依赖项:
$ go clean -modcache # Careful! $ go env -w GOPROXY=direct # direct means go directly to the source. $ go env GOPROXY direct $ time go get cloud.google.com/go/storage go: finding cloud.google.com/go/storage v1.10.0 ... real 2m6.396s user 1m51.447s sys 0m18.311s -
现在尝试启用代理(通过重置
GOPROXY为默认设置):$ go env -w GOPROXY= $ go env GOPROXY https://proxy.golang.org,direct $ go clean -modcache # Careful! $ go mod tidy # To start from the same state as before. $ time go get cloud.google.com/go/storage go: finding cloud.google.com/go/storage v1.10.0 ... real 0m10.185s user 0m9.610s sys 0m1.961s
下载cloud.google.com/go/storage耗时:
- 9分33.845秒(
GOPATH模式), - 使用模块且不使用代理耗时 2 分 6.396 秒,
- 10.185 秒,使用带代理的模块(默认)。
如果您在自己的机器上运行这些测试,结果会有所不同。但是,模块的速度要快得多GOPATH,尤其是在使用默认代理时。


