为什么功能更少的 Gogs,二进制反而比 Gitea 大?

手头有两个 Linux 二进制:Gogs 0.15.0-dev 和 Gitea 28.3.0。按直觉,Gitea 功能多得多,体积应该更大;结果 stat 一下,Gogs 123.9 MB,Gitea 119.9 MB——简单的那个反而大了 4 MB。

这不合常理。我把两个 ELF 拆开看了一遍,结论是:这不是功能问题,是发布工程问题。

第一刀:有没有 strip

file 一下就能看出差别:Gitea 显示 stripped,Gogs 显示 with debug_info, not stripped。

go version -m 也印证了:Gitea 的构建参数带 -ldflags "-s -w",Gogs 只传了版本号。Gogs 里因此保留了 13.94 MiB 的 DWARF 调试段和 4.50 MiB 符号表,合计 18.44 MiB——而且这些调试段本身还是 zlib 压缩过的。

我复制一份 Gogs,执行 strip --strip-all,大小立刻从 118.15 MiB 降到 99.71 MiB。也就是说,按同一发布标准,Gitea 反而比 Gogs 大 14.7 MiB。对绝大多数部署场景来说,这 18 MiB 调试符号只会拖慢下载和解压,真出问题时未必有人去翻 DWARF。

第二刀:静态资源怎么塞进去

strip 后的 Gogs 仍有约 100 MiB。继续看 section:Gogs 的 .rodata 高达 58.32 MiB,Gitea 只有 12.90 MiB。

原因在 go:embed。Gogs 用 -tags prod 构建,同时嵌入了两套前端:

  • 旧前端 public/{css,js,plugins,...},30.87 MiB,含 pdf.js(17.26 MiB,带源码 map)、全量 emoji、mermaid、CodeMirror;
  • 新 React SPA public/dist,22.23 MiB,仅一个 Shiki 分词 worker 就 10.13 MiB。

再加上 conf/ 和 templates/,一共 56.15 MiB 原样进二进制,不做任何压缩。我按同一 commit 重建了 public/dist,大小 23,305,878 B,并在发布二进制里匹配到了同名构建产物,确认不是估算。

Gitea 其实也内嵌了 31.30 MiB 的同类资源,但它用自定义的 bindata.dat:逐文件 gzip,压缩后只占 7.81 MiB,压缩比 4.01 倍。

同样一份前端,Gogs 花 56 MiB,Gitea 只花 7.8 MiB,差距 48 MiB。

第三刀:Gitea 的代码确实大

那 Gitea 的体积花在哪?答案是代码。

.text 45.84 MiB vs 20.83 MiB;按 pclntab 统计,Gitea 有 126,235 个函数,Gogs 只有 55,468 个;依赖数 217 vs 140。Actions、Packages、代码搜索、迁移、OAuth/LDAP 这些功能,最后都变成了机器码和类型元数据,Gitea 在“代码 + 元数据”上比 Gogs 多出约 50 MiB。

这 50 MiB,正好被 Gogs 多出来的 48 MiB 未压缩资源抵消。

结论

两个二进制最终体积几乎一样,但构成完全不同:

  • Gitea:约 90 MiB 是代码和运行时元数据,发布时已经 strip、资源已压缩;
  • Gogs:代码只有 39.5 MiB,却背了 18.4 MiB 调试符号和 56 MiB 未压缩前端。

如果 Gogs 也加 -s -w、把资源 gzip 打包、迁移完成后删掉旧 public/,体积可以落到 55–60 MiB,不到现在的一半。

这件事的教训是:Go 单文件二进制的大小,不能直接拿来衡量功能多少。是否 strip、是否压缩内嵌资源、是否处于新旧前端并存的迁移期,随便一项都能造成几十 MiB 的差异。下次再看到“谁比谁大”,先别急着下功能结论,拆开看看 section 再说。

如果你有魔法,你可以看到一个评论框~