Go vs Nim 性能实测(三):6 种 GC 模式全景对比
系列文章 (一)6 项常规算法对决 · (二)大数运算与 GMP 四端 · (三)6 种 GC 模式全景对比(本篇)
前两篇讲了常规算法与大数运算,这一篇是系列最后一块拼图:内存管理。Nim 最大的特色之一就是 GC 可选——同一份代码可以用 6 种不同的内存管理策略编译。加上 Go 的 GC,正好做一场全景对比。
测试设计
同一份 Nim 基准代码分别以 --mm:refc / markAndSweep / arc / orc / boehm / atomicArc 编译(--mm:go 本机缺 libgo.dll 不可运行、--mm:none 无 GC 不可比),与 Go 1.26.7 自带 GC 对照。7 项负载覆盖小/大对象分配、分配-释放交替、二叉树、循环引用、字符串构建、seq 增长;7 个实现校验和完全一致。
| 基准 | Go | refc | mark&sweep | arc | orc | boehm | atomicArc |
|---|---|---|---|---|---|---|---|
| allocSmall3M(300 万小对象) | 3.0 | 71.6 | 59.8 | 113.2 | 124.0 | 81.5 | 113.4 |
| allocBig1500(1500 个 1 MiB) | 97.5 | 33.4 | 132.6 | 18.5 | 18.3 | 475.5 | 18.8 |
| churn4M(400 万分配-释放) | 108.8 | 147.1 | 85.2 | 194.0 | 200.9 | 64.9 | 196.8 |
| treeBuild200(200 棵满二叉树) | 521.4 | 709.1 | 564.7 | 632.8 | 688.0 | 301.1 | 647.7 |
| cycleRefs300(300 个循环链表) | 14.2 | 15.6 | 12.6 | 12.2* | 46.4 | 8.8 | 16.6 |
| strBuild20k(2 万次字符串拼接) | 117.3 | 39.5 | 237.2 | 31.6 | 32.2 | 3311.7 | 32.7 |
| seqGrowth10M(1000 万次 push) | 102.6 | 100.7 | 99.0 | 189.7 | 190.6 | 120.2 | 193.0 |
| 7 项合计 | 965 | 1117 | 1191 | 1192 | 1300 | 4364 | 1219 |
* arc 不回收循环引用,cycleRefs 的"快"是以内存泄漏为代价;orc 有循环收集器(比 arc 慢 3.8×),追踪式全部正常回收。
读这张表的关键:没有绝对赢家
7 项负载的"最快"分布在 5 个实现上:
- 追踪式(refc / m&s / boehm / Go):分配便宜、回收周期性;引用计数(arc/orc/atomicArc):每对象即时增减引用并释放,短生命周期对象反而更贵。
- 高频小对象分配:Go 3.0 ms 遥遥领先(比 arc 快约 38×)。在 Nim 里避免用 arc/orc 做小对象流水。
- 大对象 / 字符串构建:arc/orc/atomicArc 最快(18 / 32 ms);boehm 是灾难(475 / 3312 ms,字符串拼接被慢到离谱)。
- 指针结构(树、循环、交替释放):boehm 三项全场第一(301 / 8.8 / 64.9 ms),Go 次之。
- seq 增长:refc / m&s / Go ≈ 99~103 ms;arc 系约 190 ms(扩容需搬移并维护引用计数)。
按负载选型的一句话总结:高频小对象 → Go/refc;大块内存与字符串流 → arc/orc;大量指针结构 → boehm/Go;循环引用 → Go/orc/refc(arc 泄漏勿用);seq 频繁增长 → m&s/Go;综合均衡 → orc(Nim 2.x 默认)。
系列总结与选型建议
把三篇五轮测试放一起,可以给出相当务实的结论:
| 场景 | 推荐 | 理由 |
|---|---|---|
| 常规计算密集算法 | Nim / Go 皆可 | 6 项合计仅差 3.5%,按生态选 |
| 高频小对象分配 | Go | 比 Nim 最快方案还快一个数量级 |
| 大数乘法/模幂(自己写) | Go math/big | 深度优化碾压生态库;Nim 需自研算法级优化才能接近 |
| 大数模幂(可引 GMP) | Nim+GMP FFI / C | FFI 开销≈0,GMP powm 快 58% |
| 除法/开方密集(π 类) | GMP | 比 math/big 快 7.8~11.3×,开方段 91× |
| 高性能 FFI 调用 | Nim dynlib | cgo 在百万次小调用场景慢 45% |
| 默认 GC 选择 | Nim 用 orc;高频小对象换 refc | 无全局最优 |
一句话总结:常规负载 Go 与 Nim 打平;一旦进入大数、FFI 调用模式、GC 形态这些"深水区",差距就不是语言本身的锅,而是运行库与算法选择的锅——Go 的 math/big 和 GC 打磨得深,Nim 的 ARC/FFI 和 C 后端也有自己的主场。
局限说明
- 单机单测:i9-11950H + Windows 10,其他平台/版本结果可能不同。
- 单线程对比,未测多核、并发与内存峰值。
- GC 负载结果与 Nim 版本强相关(Nim 2.x 各 –mm 仍在演进),升级大版本后建议重测。
- 测试工程位于本机
benchmark/nimgolang,复现脚本见第一篇。