在 Ubuntu 上做 Go 项目开发时,编译速度一旦掉下来,通常不会只卡在某一个点:可能是 go build 没把多核吃满,也可能是依赖下载、缓存位置、磁盘读写甚至 cgo 一起拖慢了整个流程。与其盲目堆参数,不如按“编译命令、代码结构、环境硬件”三个层次逐步排查,这样更容易判断哪一类优化最值得先做。
下面这篇文章把常见方案按场景拆开:哪些选项能直接缩短构建时间,哪些改动能减少重复编译,哪些则更适合依赖多、Docker 构建频繁或包含 cgo 的项目。看完之后,你可以根据项目规模和机器配置,快速选出优先级最高的几项来落地。
先从编译参数入手:最快看到效果的一组优化
如果你现在最直接的问题是“每次 go build 都很慢”,优先检查命令参数通常最划算。它们不需要大改项目结构,但往往能立刻带来可见收益。
1. 用 -p 打开并行编译
Go 可以并行编译多个包,关键是让编译任务和 CPU 核心数匹配。常见用法是:
go build -p 4
如果机器是多核 CPU,建议把并行数设为核心数的 70%~80%,既能提高利用率,也能避免系统因为抢占资源而明显卡顿。对 4 核、8 核这类桌面开发机来说,这通常是最容易见效的优化项。
2. 固定 GOCACHE,让缓存真正发挥作用
Go 默认启用了编译缓存,但缓存目录如果不稳定,或者环境频繁变动,效果会被削弱。可以显式指定缓存路径:
export GOCACHE=$HOME/.cache/go
这样做的核心价值在于复用未变更代码的编译结果。项目越大、依赖越多,第二次及后续构建的收益越明显。
3. 不需要调试时,用 -ldflags="-s -w" 缩短链接时间
如果当前构建目标是发布包,而不是调试版本,可以去掉符号表和 DWARF 调试信息:
go build -ldflags="-s -w"
这一做法通常会让二进制体积缩小 30%~50%,同时减少链接阶段开销。对持续集成和发布构建尤其有价值,但如果你需要排查线上问题或做本地调试,就不要默认一直带着它。
4. 需要多次链接时,考虑只编译不链接
在特定场景下,可以用 -c 只生成目标文件 .o,先把编译和链接拆开:
go build -c
这更适合需要多次链接、或先分别生成模块目标文件再统一处理的流程。日常业务项目未必常用,但在构建链条较复杂时可以减少重复工作。
5. 用 -gcflags="-l=4" 做更激进的编译优化
如果你更看重生成代码的执行效率,而不是只盯着编译耗时,可以尝试:
go build -gcflags="-l=4"
文中的前提是默认值为 -l=1,最大到 4。它对编译时间影响不算特别大,但更适合对运行性能要求高的场景。换句话说,这不是“让编译飞起来”的万能开关,而是偏向生成结果优化的一项参数。
编译慢不一定是命令问题,很多时候是代码和依赖范围太大
如果你已经调过参数,但编译还是慢,就该看项目结构了。因为编译器真正花时间的,常常不是命令本身,而是它不得不处理的大量文件和依赖关系。
1. 大包拆小,减少每次扫描和重编译的范围
一个包如果塞进几千个文件,编译器光是扫描和分析就会花掉不少时间。更合理的做法,是按功能边界拆成多个小模块,让改动只影响局部包,而不是牵动整个工程。
这种优化的好处不只体现在单次构建速度上,还能降低后续维护成本。包边界清晰后,编译缓存和增量编译也更容易发挥作用。
2. 避免循环依赖,别让编译器反复处理同一批文件
循环依赖会让编译过程变得低效,复杂度甚至可能明显上升。常见解决方式包括提取公共接口、下沉共享能力,或者重新梳理依赖方向。
这类问题修完之后,收益通常比较稳定,因为它不是参数层面的“加速”,而是从依赖图本身减少无用工作。
3. 依赖稳定或离线环境下,用 vendor 缩短取依赖时间
如果项目依赖相对固定,或者构建环境经常受网络影响,可以执行:
go mod vendor
这会把依赖复制到项目根目录的 vendor 文件夹,后续编译优先读取本地文件。它尤其适合离线构建、内网环境,或者那些不希望每次构建都受远程仓库波动影响的场景。
软件调完还是慢,再看硬件短板
Go 编译既吃 CPU,也吃磁盘 I/O。参数和结构优化能缓解问题,但如果机器本身存在明显短板,软件层面的收益最终会碰到天花板。
1. 多核 CPU 对 Go 并行编译很友好
Go 的并行编译机制能比较充分利用多核处理器。文中给出的经验是,从 4 核升级到 8 核,编译速度提升往往接近线性,前提是已经正确使用了 -p 参数。
如果你经常本地编译大型项目,CPU 往往是最直接的性能来源。
2. 内存不足会把问题放大成 swap 灾难
大型项目构建时,一旦内存不够,系统会频繁触发磁盘交换,编译时间会急剧上升。文中的建议是至少 8GB,16GB 及以上更稳妥。
这类瓶颈的特点是:你可能看到 CPU 没跑满,但构建依然很慢,因为时间都耗在等待 I/O 上了。
3. SSD,尤其是 NVMe SSD,对构建体验提升很明显
机械硬盘在随机读写上的短板,对依赖下载、源码扫描和临时文件读写都很不友好。换成 NVMe 协议的 SSD 后,这些环节的速度通常会有数量级提升。
如果你的项目频繁清缓存、重拉依赖或在容器里反复构建,磁盘性能带来的差距会更明显。
环境和工具链也会拖慢构建,尤其是在 Ubuntu 开发机上
很多人把“编译慢”都归到编译器头上,但在 Ubuntu 实际使用中,网络、Go 版本和额外工具同样会决定你的等待时间。
1. 先把 GOPROXY 设好,减少依赖下载卡顿
国内环境访问 golang.org/x/ 等模块时,经常不是编译慢,而是取依赖慢。可以直接配置:
go env -w GOPROXY=https://goproxy.cn,direct
这类优化对首次构建、新机器初始化和 CI 环境最明显。很多“go build 很慢”的抱怨,实际上都耗在了模块下载阶段。
2. Go 版本别太老,1.10 之后编译性能已有明显改进
文中提到,Go 1.10 之后编译器性能改进已经很明显。保持在最新稳定版,通常意味着更快的代码生成、更合理的依赖解析,以及更成熟的工具链行为。
如果你还在维护历史环境,先确认是否真有必要停留在旧版本;否则,升级往往是成本最低的整体优化。
3. 项目用了 cgo,就把 ccache 也纳入链路
一旦项目里混入 C/C++ 代码,仅优化 Go 侧通常不够。可以先安装 ccache:
sudo apt install ccache
然后把它放进 PATH:
export PATH="/usr/lib/ccache:$PATH"
这样 C 编译结果就能被缓存,适合 cgo 调用频繁的项目。对于这类工程,真正的瓶颈往往是混合编译链,而不是单纯的 Go 编译器。
几种按场景使用的进阶技巧
如果前面的常规项已经做完,或者你面对的是 Docker、发布包压缩这类更具体的场景,可以再考虑下面几种方式。
1. 利用 Go 默认的增量编译能力
Go 默认就支持增量编译,只会重编译修改过的文件,这一点本身不需要额外配置。文中补充的做法是配合 -i 选项安装依赖,以减少后续构建中的重复下载。
因此,增量编译更依赖你是否频繁破坏缓存、是否把依赖和构建流程设计得足够稳定。
2. Docker 构建要靠分层,把下载依赖和业务代码分开
在 Dockerfile 中,应该尽量把 go mod download 和 go build 放在不同层。依赖没有变化时,Docker 可以直接命中缓存层,避免每次重新下载和全量编译。
这一点对 CI/CD 尤其关键。很多容器构建慢,并不是 Go 本身慢,而是镜像层设计让缓存始终失效。
3. 发布阶段可以用 UPX 压缩,但别把它当编译加速手段
如果你的目标是缩小交付体积,可以安装 upx:
sudo apt install upx
然后压缩二进制,例如:
upx --best --lzma main
文中的经验是体积可缩小 50%~70%。但它会增加启动时间,所以更适合对启动时延不敏感的后台服务。需要注意的是,这解决的是分发体积问题,不是编译本身的问题。
实用建议:按“命令、结构、环境”顺序排查最省时间
如果你只是想尽快把 Ubuntu 下的 Go 编译速度提上来,可以按这个顺序动手:
- 先调
-p、GOCACHE和-ldflags="-s -w",看本地构建时间有没有立刻下降; - 再检查包是否过大、有没有循环依赖、是否适合引入
vendor; - 最后看网络代理、Go 版本、
cgo缓存,以及 CPU、内存、SSD 是否已经成为瓶颈。
这样排查的好处是,既能先拿到快速收益,也能避免把“网络慢”“磁盘慢”误判成 Go 编译器本身的问题。












