大家好,我是Tony Bai。

近年来,如果你常年混迹于国内外各大技术社区,你一定会感受到一种近乎狂热的“政治正确”:带垃圾回收(GC)的语言都有原罪,万物皆可(且应该)用 Rust 重写

从底层基础设施到上层业务逻辑,无数团队在遇到性能瓶颈时,脑海中蹦出的第一个念头就是:“Go/Java 搞不定了,由于 GC 停顿的存在,我们必须换 Rust 乃至 C++ 来重构核心模块。”

但这真的是解决性能问题的唯一出路吗?

最近,几位硅谷顶级的技术大佬——前 Tailscale CTO David Crawshaw、开源时序数据库 VictoriaMetrics CTO Aliaksandr Valialkin,以及资深底层代码大牛 Stewart Lynch,在 X(原推特)上掀起了一场关于“现代软件复杂性与性能优化”的讨论。

仔细研读他们的观点后,我得出了一个可能有些“反直觉”的结论:

对于绝大多数商业项目而言,盲目追求去 GC 化和无脑 Rust 重写,是一场灾难。真正顶级的性能优化,往往只需要对那 1% 的“热路径”动刀。

今天,我们就来揭秘这层信息差,看看顶级架构师是如何在不增加心智负担的前提下,把带 GC 的 Go 语言性能压榨到极致的。

现代软件最大的毒瘤:“不必要的复杂性”

为什么我们总是忍不住想要用极其复杂的语言或架构去重写现有的系统?

Stewart Lynch 在讨论中一针见血地指出了现代软件工程的通病:"Everything that's wrong with modern software can be summed up in two words: Unnecessary Complexity"(现代软件所有的毛病,可以用两个词来概括:不必要的复杂性)。

这背后其实隐藏着一个程序员群体独有的心理学陷阱。

Lynch 解释道:“程序员这个群体有一个特殊的问题——我们往往是因为‘享受解决复杂问题’才选择这个职业的。我们热衷于理解极其复杂的东西并让它运转起来,我们是人类历史上最复杂结构的构建者。正因为如此,我们在任何地方都在寻找与复杂性搏斗的机会,即使在那些本该追求极简的地方。

这就解释了为什么很多团队在面对一个简单的 CRUD 业务或者中等并发的微服务时,会毫不犹豫地引入极高门槛的语言(比如有着严苛借用检查器的 Rust)或是过度设计的服务网格。

因为复杂,让人觉得高级

但结果是什么?

业务逻辑被切割得支离破碎,新员工入职需要花费两三个月才能看懂生命周期和指针所有权,团队的迭代速度断崖式下跌。你以为你在优化系统的性能,实际上,你在制造一场长期的维护灾难。在这个过程中,你消耗了大量的公司预算,仅仅是为了解决那些“想象中的未来问题”。

记住架构设计的第一法则:复杂性是优秀软件的死敌。

你的 99% 代码根本不需要瞎折腾

既然复杂性是死敌,那性能问题怎么办?难道我们就任由 GC 导致程序卡顿吗?

这时候,前 Tailscale CTO David Crawshaw 抛出了一个极具颠覆性的观点。他指出,整个行业现在正把海量的资源倾注到像 Rust 这样没有 GC 的程序中,但大家忽略了一个极其残酷的统计学事实:

“Almost all your code paths are cold and GC is net positive. 1% of your code is performance sensitive. Don't create GC pressure there.” (你几乎所有的代码路径都是‘冷’的,在这些地方 GC 带来了纯粹的正向收益。只有 1% 的代码对性能真正敏感。你只需要不在那 1% 的地方制造 GC 压力就行了。)

什么是“冷代码”?

配置解析、路由分发设置、错误处理、数据库连接初始化、日志记录……在一个庞大的工程中,这部分代码占据了 99% 的体积。它们对微秒级的延迟根本不敏感。

对于这 99% 的代码,使用 Go、Java 甚至 OCaml 这样带有Full runtime GC的语言,是巨大的恩赐。GC 解放了程序员的大脑,让你不需要像写 C/C++ 或 Rust 那样,在写每一行代码时还要在脑海里进行“部分编译时规划(Partial compile-time planner)”。它让你可以把全部精力聚焦在“业务逻辑”本身。

人类解决复杂问题的能力,在不被内存分配分心时,才能发挥到极致。

为了那 1% 真正需要榨干 CPU 周期的核心逻辑,去强迫整个团队在剩下 99% 的冷代码中也要与内存所有权作斗争,这在商业 ROI(投资回报率)上是极其荒谬的。

这就是所谓“不要为了 1% 的醋,去包 99% 的饺子”。

VictoriaMetrics CTO 的 1% 极简榨干指南

好,逻辑理顺了:我们决定坚持使用 Go 语言,享受它极高的开发效率和并发优势。但我们确实遇到了那 1% 的核心瓶颈——比如高频交易的核心撮合引擎、时序数据库的底层写入循环。这部分代码极其吃 CPU,且 GC 带来的 STW(Stop The World)让人无法忍受。

不换语言,怎么破局?

别急,让我们来看看目前世界上性能最强悍的开源时序数据库之一:VictoriaMetrics 的做法。这个数据库完全是由 Go 语言编写的,但在各项 Benchmark 性能测试中,它经常把一众 C++ 和 Rust 写的时序数据库按在地上摩擦

它的 CTO,Aliaksandr Valialkin 在这次讨论中,大方地分享了他的优化路径。我将他的经验,结合各位大牛的讨论,为你整理成了以下三步走的“实操密码”:

放弃盲猜,用 Profiler 精准定位热路径(Hot Paths)

你永远不可能靠“直觉”找到性能瓶颈。Aliaksandr 强调,Go 语言拥有极度强大的内置 Profiler(pprof)。不要一上来就重构,先让程序跑起来,打入真实流量,然后用 pprof 精准定位出那消耗了 80% CPU 和大量内存分配的 1% “热路径”究竟在哪几个函数里。

这 1% 的代码,代码量往往极小,寻找它们并不困难。

在热路径中“完全移除”内存分配(Zero Allocation)

这是 Go 性能优化的核心灵魂。Aliaksandr 的原话是:“This is how I optimize programs written in Go - by removing memory allocations from hot paths...”。

只要你在热路径中不产生新的对象(不触发 malloc 和堆分配),垃圾回收器(GC)就根本不会被唤醒。没有分配,就没有垃圾;没有垃圾,就没有 GC 压力和停顿。

开启“逃生舱”:使用预分配与 Arena 机制

既然热路径不能分配新内存,那需要处理海量数据怎么办?大佬们给出了三种在 Go 中模拟底层语言内存管理的“逃生手段”:

  • 预分配大块内存(Pre-allocations): 正如 David Crawshaw 所举的例子,你可以在 Go 中一次性分配一个巨大的数组,比如:var x = make([]struct{...}, 1e6)。 这只产生一次大分配,然后你完全可以利用自己的算法,在这个预先分配好的内存块中进行指针的滑动和复用。对于 GC 来说,这只是一个单一的连续指针,GC 扫描它的成本极低,既能实现高并发,又极大地降低了 CPU 消耗。

  • 对象池机制(sync.Pool): 对于频繁创建和销毁的小对象,不要让它们落入 GC 的魔爪。利用 sync.Pool 将它们缓存起来,反复复用。

  • 请求作用域内存竞技场(Arenas): Aliaksandr 提到了在处理网络请求时极其高效的 Arena 概念。在这个模式下,与单次 Request 相关的所有小对象分配,都在一个预先分配好的大块 Arena 中进行。当请求结束时,不需要逐个去释放对象,而是直接清空(free)整个 Arena。这几乎达到了和 Rust 一样零开销的内存清理效果,但代码写起来依然是熟悉的 Go 语法。(注:Go官方的arena包试验失败是因为与存量Go代码的API融合问题。)

对 99% 的代码保持克制

当你在那 1% 的热路径里用尽了上述像 C 语言一样的“脏活累活”后,请立刻停手

让程序剩下的 99% 保持最地道(Idiomatic)、最简单、最具可读性的 Go 代码。让 GC 去接管它们。

你会神奇地发现:你的程序不仅拥有了媲美 C++/Rust 的极致性能,同时你的团队依然保持着原本极高的业务迭代速度。

小结——顶级工程师与普通码农的终极分水岭

回顾这几位大佬的讨论,其实核心只指向了一个词:克制(Restraint)。

普通工程师总是试图寻找一种“银弹”——希望换一种时髦的语言,就能一劳永逸地解决架构、性能和内存安全的所有问题。他们沉迷于构建极其复杂的抽象体系,试图用技术上的炫技来掩盖业务上的平庸。

而真正顶级的架构师,深知商业的本质和团队运作的规律。他们懂得:

  1. 好的设计,就是当你不能再拿走任何东西的时候。 (正如评论区一位开发者所说:Good design is when you keep taking away things until you cannot take away any more.)

  2. 永远不要在全局引入复杂性。 遇到性能问题,先用监控定位,然后把性能敏感的那 1% 的代码隔离出来,在这个小黑盒子里用最极客的方式优化,最后把它严丝合缝地封装好。

  3. 拥抱不完美但高效的工具。 不要嫌弃 GC,懂得如何与 GC 和谐共处,才是真正的大师。

如果下次你的团队里,再有人因为某个接口慢了 10 毫秒,就嚷嚷着要用 Rust 把整个几十万行的后端服务重写时,请把这篇文章甩到他的脸上。

告诉他:“去把 pprof 打开,把那 1% 循环里的临时变量给我复用了,然后早点下班回家。”

资料链接:

  • https://x.com/valyala/status/2055725885035045234

  • https://x.com/stewartlynch8/status/2055322205563617516

  • https://x.com/davidcrawshaw/status/2055288855792955511


👇 今日互动探讨:

在你的职业生涯中,是否经历过为了追求所谓的“极致性能”或“极客审美”,而导致整个项目陷入“过度复杂化(Over-engineering)”灾难的时刻?或者,你在使用 Go 语言时,有什么私藏的“热路径”压榨技巧?

欢迎在评论区留言和我探讨,我们一起对抗现代软件的“过度复杂病”。 (如果你觉得这篇文章打破了你的认知,别忘了点赞转发,让更多挣扎在重构边缘的兄弟们看到!)


点击下面标题,阅读更多干货!

-  从arena、memory region到runtime.free:Go内存管理探索的务实转向

Zig: 这才几天啊,我就被Rust替掉了😭

AI 时代,软件大师们为什么都倒戈向 Go 和 Rust 了?

“用 Go 打天下,用 Rust 救火”:这才是 2026 年后端架构的唯一正解

谁说 Rust 在中国火了?扒开 2025 全年数据,我看到了令人尴尬的真相

从入门到极致:VictoriaMetrics 教你写出最高效的 Go 代码

日志查询从70小时到10秒?VictoriaMetrics联创揭示PB级日志处理性能奥秘

聊聊为什么我要花这么大精力,带大家手写 Agent Harness?


🔥 还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 从0 开始构建 Agent Harness 将带你:

  • 抛弃臃肿框架,回归“驾驭工程 (Harness Engineering)”的第一性原理

  • 用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等,复刻极简OpenClaw

  • 构建坚不可摧的 Safety Middleware 与飞书人工审批防线

  • 在底层实现 Token 成本审计、链路追踪与自动化跑分评估

  • 从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”

扫描下方二维码👇,开启从 0 开始构建Agent Harness 的实战之旅。

更多推荐