关于 go-binary-size
go-binary-size 是一套通过测量、逐项调整和验证来缩小 Go 二进制文件的流程指南,面向 Go 开发者。内容涵盖链接选项、CGO、构建标签、依赖、嵌入资源和容器,并要求记录体积变化及代价。
主要功能
- 01先测量基线和压缩体积
- 02分析包与符号占用
- 03逐项评估构建选项
- 04检查 CGO、构建标签和依赖
- 05评估嵌入资源与容器镜像
- 06运行二进制和测试进行验证
适用场景
- 01减小 Go CLI 发布文件
- 02评估剥离符号对调试的影响
- 03比较 CGO 开关前后的体积
- 04精简 Go 应用的容器镜像
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ eduardo-sl/ go-agent-skills --skill go-binary-size安装后在 Claude Code 中输入/go-binary-size确认已加载。
详细介绍
Go 二进制文件大小
标准 Go 二进制文件包含运行时、垃圾回收器、完整的符号表和行号表,以及所有可传递到达的包。小型 CLI 的大小通常为 8-15 MiB。大部分内容都可以移除,但必须通过测量来判断——猜测哪个依赖占空间通常几乎总是错的。
操作流程
没有前后对比数据,绝不应用任何标志。
- 构建基线并记录其大小。
- 找出字节都用在了哪里。
- 一次只应用一类更改,并在每次更改后测量。
- 验证二进制文件仍能运行,且测试仍能通过。
- 报告“更改 → 节省的字节数 → 代价”表格。
1. 先测量
# Baseline, reproducible
CGO_ENABLED=1 go build -trimpath -o /tmp/base ./cmd/app
ls -l /tmp/base
# Which packages and symbols cost the most
go tool nm -size -sort size /tmp/base | head -40
# Package-level attribution (third-party, more readable)
go install github.com/Zxilly/go-size-analyzer/cmd/gsa@latest
gsa --web /tmp/base
go version -m /tmp/app 会打印二进制文件中记录的模块列表和构建设置,可用于确认发布版本实际使用了哪些标志。
如果制品会随容器层或发布归档一起分发,也要测量压缩后的大小。剥离符号后的收益在 gzip 压缩后会变小;移除依赖带来的收益则更大。
gzip -c /tmp/base | wc -c
2. 剥离符号和 DWARF——单项收益最大
go build -ldflags="-s -w" -trimpath -o /tmp/stripped ./cmd/app
通常可减少原始大小的 25-35%。
具体代价如下:
- ✅ Panic 消息和 goroutine 堆栈跟踪仍可正常工作。运行时使用自己的
pclntab,而-s -w不会移除它。 - ❌
dlv和gdb将无法再解析源代码行。不要将剥离过符号的二进制文件部署到计划连接调试器的环境中。 - ⚠️ 一些依赖外部符号化的性能分析和崩溃报告工具会受到影响。进程内的
net/http/pprof不受影响。
保留每个发布构建未剥离符号的副本,以便事后分析。
如果不需要 VCS 标记,可添加 -buildvcs=false。它节省的空间不多,但也会从分发的制品中移除提交元数据。
3. 禁用内联——权衡后再决定
go build -ldflags="-s -w" -gcflags=all=-l -o /tmp/noinline ./cmd/app
还可额外减少 5-10 个百分点,但会降低热点路径的运行时性能。对于启动后完成一项工作便退出的 CLI,这可以接受。对于对延迟敏感的服务,如果未先通过基准测试评估性能退化,则不可接受。
4. CGO 和运行时
CGO_ENABLED=0 go build -tags netgo,osusergo -ldflags="-s -w" -o /tmp/pure ./cmd/app
这三个选项应配套使用:禁用 cgo 但不启用 netgo,osusergo,构建仍会依赖 C 解析器存根。
在假设它有帮助之前,先检查:
go list -deps ./... | xargs go list -f '{{.ImportPath}} {{.CgoFiles}}'会显示哪些包实际使用了 cgo。- 禁用 cgo 可能会让二进制文件变大,因为纯 Go 的 C 绑定替代方案可能体积更大。请分别测量。
- 如果发布配置已设置
CGO_ENABLED=1或-linkmode=external,那必然有其原因。更改前先查明。
如果必须保留 cgo,且项目会编译 C 源码(SQLite 绑定、图像编解码器),则 CGO_CFLAGS="-Oz" 可优化这些 C 代码以减小体积。
5. 构建标签——最常被跳过的一步
重量级可选功能通常由 Go 源码之外的标签控制。
grep -rn '//go:build' --include='*.go' . | grep -v _test.go
grep -rnE '\-tags' Makefile Taskfile.y*ml .goreleaser.y*ml Dockerfile .github/workflows/ 2>/dev/null
常见的优化包括:移除未使用的驱动程序、在生产构建中排除管理界面、构建运行时获取资源的 noembed 变体。
6. 依赖体积
单个导入就可能占据二进制文件的大部分。gsa 可以按模块统计字节数——从这里入手,而不要凭直觉判断。
常见的问题依赖:
- 云服务提供商 SDK。导入单个服务包,不要导入聚合根包。
github.com/prometheus/client_golang为了少量计数器引入了大量功能。- 任何大量使用反射的依赖:链接器无法通过
reflect执行死代码消除,因此基于反射的编解码器会保留无人调用的类型。 - 不使用的 proto 所对应的生成版 protobuf 包。
用 40 行标准库代码替换依赖,是合理的体积优化。但用自己编写的加密代码替换维护良好的依赖则不是。
7. 嵌入资源
//go:embed 内容以未压缩形式存储。
//go:embed assets/*
var assets embed.FS
按优先顺序,可选方案包括:减少随附资源;预先压缩资源并使用 Content-Encoding: gzip 提供;将资源完全移出二进制文件,放入容器镜像或 CDN。
8. 容器镜像
二进制文件往往只占问题的一半。
FROM golang:1.25 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/app
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
scratch 比 distroless/static 更小,但不包含 CA 证书、
/etc/passwd 或时区数据库。除非已确认二进制文件不需要这些内容,
否则请使用 distroless/static。
9. UPX — 最后手段,通常不是好选择
upx --best 大约能将磁盘占用减半,但每次启动都要进行解压,会破坏
基于 mmap 的工具,并且很容易触发杀毒软件和 EDR 的启发式检测。不要对
面向最终用户发布的二进制文件,或在受监控的生产环境中运行的二进制文件进行打包。
仅在尺寸受限的嵌入式目标上考虑使用,并在报告中明确说明。
验证
每次更改后:
go build -o /tmp/candidate ./cmd/app && /tmp/candidate --version
go test ./...
ls -l /tmp/base /tmp/candidate
无法启动,或丢失了由构建标签控制的功能的较小二进制文件,并不算成功。
验证清单
- 在更改任何标志之前记录基准大小
- 每项声称的节省都提供变更前后的数字,包含原始大小和压缩后大小
- 已应用
-s -w,并保留未剥离的构建产物以便调试 - 对延迟敏感的代码已对
-gcflags=all=-l进行基准测试,而非凭假设判断 - 已分别测量
CGO_ENABLED=0开启和关闭时的情况,并非盲目应用 - 已检查 Makefile、goreleaser、Dockerfile 和 CI 工作流中的构建标签
- 使用工具进行了依赖归因,而非凭直觉判断
- 候选二进制文件可以运行,且测试套件通过
- 仅在有明确理由时使用 UPX