mcpField
G
go-binary-size

go-binary-size

github.com/eduardo-sl开发8373 次安装2 周前更新
代码仓库

关于 go-binary-size

go-binary-size 是一套通过测量、逐项调整和验证来缩小 Go 二进制文件的流程指南,面向 Go 开发者。内容涵盖链接选项、CGO、构建标签、依赖、嵌入资源和容器,并要求记录体积变化及代价。

主要功能

  1. 01先测量基线和压缩体积
  2. 02分析包与符号占用
  3. 03逐项评估构建选项
  4. 04检查 CGO、构建标签和依赖
  5. 05评估嵌入资源与容器镜像
  6. 06运行二进制和测试进行验证

适用场景

  1. 01减小 Go CLI 发布文件
  2. 02评估剥离符号对调试的影响
  3. 03比较 CGO 开关前后的体积
  4. 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. 构建基线并记录其大小。
  2. 找出字节都用在了哪里。
  3. 一次只应用一类更改,并在每次更改后测量。
  4. 验证二进制文件仍能运行,且测试仍能通过。
  5. 报告“更改 → 节省的字节数 → 代价”表格。

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

无法启动,或丢失了由构建标签控制的功能的较小二进制文件,并不算成功。

验证清单

  1. 在更改任何标志之前记录基准大小
  2. 每项声称的节省都提供变更前后的数字,包含原始大小和压缩后大小
  3. 已应用 -s -w,并保留未剥离的构建产物以便调试
  4. 对延迟敏感的代码已对 -gcflags=all=-l 进行基准测试,而非凭假设判断
  5. 已分别测量 CGO_ENABLED=0 开启和关闭时的情况,并非盲目应用
  6. 已检查 Makefile、goreleaser、Dockerfile 和 CI 工作流中的构建标签
  7. 使用工具进行了依赖归因,而非凭直觉判断
  8. 候选二进制文件可以运行,且测试套件通过
  9. 仅在有明确理由时使用 UPX

评论

支持 Markdown