
先说结论这个标题讲的事情本身是真的但你如果只看性能狂飙17倍这几个字很可能把它理解成一次单纯的胜利。我的看法恰恰相反——这是一个非常典型的案例用来讨论大模型辅助底层优化时的边界在哪。DHH 的做法本质上是用大模型Claude 或 ChatGPT 之类的编码助手把一段 Rust 热点代码重写成了高度特化的汇编 / SIMD 版本测试下来性能提升了 17 倍。单看数字确实吓人工程上平时能稳定拿到 30% 的优化都算大活了17 倍完全是另一个量级。但与此同时社区里批评工程倒退的声音也不是没有道理当一段 Rust 被降级成手工汇编你等于把 Rust 好不容易建立起来的安全边界、可移植性、可维护性全部拆掉只换回一个极其狭窄场景下的性能数字。这篇文章我不想只复述新闻。我会拆一下 17 倍可能从哪来拆一下这种优化的风险到底在哪儿然后结合我自己做性能优化的经验聊聊什么时候应该学 DHH什么时候千万别学。1. 事件复盘大模型把 Rust 代码降级成汇编17倍是怎么来的1.1 所谓手撕成汇编到底做了什么事DHH 在 Once 项目里一直在尝试用 AI 辅助编程这次的场景我记得很清楚他遇到一段 Rust 写的热点代码跑起来不够快于是把这段代码扔给大模型要求它给出性能更强的版本。大模型最终给出的结果里出现了汇编级的内容——不是内联汇编一小段而是把核心计算逻辑整个重写成面向特定指令集的实现。这个把 Rust 手撕成汇编的说法很容易让人误解好像大模型真的在逐行翻译汇编。实际上更可能是两种形式第一种是调用 Rust 里对应的 CPU intrinsic也就是core::arch下那些直接映射到单条 CPU 指令的函数。你写的还是 Rust 代码但每条语句对应一条汇编指令编译器基本只是搬运工。第二种是直接上asm!内联汇编。Rust 是支持稳定版内联汇编的可以用core::arch::asm!嵌入手写汇编片段绕过高层的抽象。不管哪种形式核心思路都是绕过编译器、绕过类型抽象直接在指令层面控制 CPU 做什么。这对 Rust 这种强调安全性和零成本抽象的语言来说属于打开盖子看引擎的操作。为什么 DHH 会想到让大模型做这件事因为他需要的不是通用性能优化而是某个具体算法在具体数据集上的极致表现。这种优化传统的写法很难一次到位要不停看汇编、查指令延迟、调内存布局。大模型恰好擅长这种给定目标不断贴近特定指令集的任务它不需要像人一样被Rust 应该怎么写的经验束缚反而更容易跳出常规玩法。1.2 17倍性能提升的主要来源SIMD、循环展开与指令级特化很多人看到 17 倍会下意识觉得是在造假或者觉得原来的 Rust 代码写得太烂了。确实基线实现太差会导致倍数虚高但 DHH 用的基线并不是随便的烂代码我认为 17 倍主要来自几个因素的叠加。SIMD 是最常见的大头。如果你原来的代码是逐个字节处理数据每次算一个字节而新的版本用 AVX-2 或 AVX-512 一次处理 32 字节甚至 64 字节理论上能拿到的加速比就是 16 或 32 倍。再配合循环展开减少分支和依赖停顿叠加起来轻松超过 10 倍。这跟语言无关C 里用 SIMD 也能拿到同样效果问题只是很多人不习惯写 intrinsic。指令级特化也能贡献很大。比如计算 CRC 校验现代 CPU 硬件上有专门的crc32指令一条指令替代原来一大段查表和异或逻辑吞吐直接质变。又比如做位图操作pext、pdep这类 BMI2 指令能把一次复杂的位提取转换成单条指令。大模型非常喜欢在这种场景下用专用指令因为它能看到这段逻辑等价于哪个指令而普通开发者脑子里没存这么全的指令表。内存布局优化也经常被忽视。原来的代码可能是按结构体数组存的访问起来缓存不友好优化后的版本可能改成数组结构体遍历变成顺序访存预取效率高很多。这个方向不是汇编独有的但大模型在写汇编版本时往往会顺手把数据结构也改成适合 SIMD 的排布于是收益叠加。编译器保守优化与激进优化的差距。Rust 默认在-O下的自动向量化其实不算激进因为编译器必须保证代码语义不变不能假设别名和溢出的行为。大模型生成的版本直接写死了内存布局和对齐方式告诉编译器我保证这些都合法编译器就能彻底放开手脚。本质上大模型帮我们做了编译器不敢做的假设。要说明的是这些收益的前提是代码目标非常明确。17 倍这个数字大概率是在特定 CPU、特定数据分布、特定编译器版本的基准测试上测出来的。一旦换一个 CPU 型号、换一组分布完全不同的输入数据甚至换了编译器版本结果可能就大幅缩水。这不是说 DHH 造假而是所有激进底层优化的通病你越贴近硬件优化就越脆弱。2. 性能狂飙的另一面为什么说这是工程倒退2.1 可维护性与可移植性的代价清单如果只看性能17 倍无论如何都是胜利。但从工程角度代价也非常清晰。我这里列一个清单都是真实项目里会踩到的问题可读性骤降。用 SIMD intrinsic 写出来的代码每一行都是_mm256_loadu_si256、_mm256_shuffle_epi8这种它的语义是加载、洗牌、运算不是解析这段数据。半年以后回去维护连原作者都可能要查半天手册更别说新人接手。平台绑定。这段代码通常是 x86_64 特定的。你用的是core::arch::x86_64它编译不进 ARM64。Rust 跨平台的优势在这个文件里直接归零。如果你有 macOS ARM 的开发者、Linux ARM 的服务器或者未来切到 RISC-V这段代码全都要重写。编译器优化形同虚设。intrinsic 和汇编告诉编译器按我的指令来这相当于放弃了编译器后续版本带来的自动优化。主流编译器每年都在改进自动向量化和指令调度普通代码躺着拿到新 CPU 的性能提升汇编代码反而拿不到。CPU 型号差异导致的不确定性。支持 AVX-512 的 CPU 和不支持的老 CPU 行为完全不同。你用 AVX-512 写的代码在支持它的机器上确实快在不支持它的机器上要么直接非法指令崩溃要么走 fallback 慢到怀疑人生。某些 CPU比如部分 Intel 型号在重负载下还会因为 AVX-512 功率墙自动降频性能不升反降。我整理了一个对比表方便看得更清楚维度Rust 普通实现大模型生成的汇编/intrinsic 版本性能受限于编译器自动优化可能获得数量级提升可移植性跨平台良好绑定特定指令集可读性高业务语义清晰低指令语义掩盖业务语义调试体验可用正常断点、日志、sanitizer断点经常定位到指令级别sanitizer 作用有限编译器演进自动获益基本冻结团队协作普通开发者可维护需要极少数精通底层优化的专家测试复杂度常规测试即可需要针对不同 CPU 维护多版本并交叉验证2.2 从优化到技术债是怎么发生的所谓技术债不是说一段代码难读就叫技术债而是它对未来的变更产生持续阻力。DHH 这次优化的代码如果哪天业务逻辑变了比如数据格式从 16 字节变成 20 字节或者增加了一个字段那么改这段汇编级别的代码几乎等于重写。更重要的是验证成本的上升。普通 Rust 代码有所有权和类型系统兜底编译器帮你挡住一大类错误。一旦写成汇编/intrinsic类型系统能帮你做的就很有限了很多内存布局问题、溢出问题、未对齐访问问题都需要在运行时才能暴露。你可能需要额外写差分测试把优化版和原版的输出逐一比对确保成千上万条测试数据的输出完全一致。这还没算指令集兼容性套餐——运行时要做 CPU feature 检测、要走 fallback、要在 CI 里跑多架构矩阵。我见过很多团队从一段性能卓越的底层优化代码起步最后搭出一整套校级基础设施来维护它包括跨平台编译脚本、多版本调度逻辑、专门的回归测试集维护成本早已超过当初省下的那点 CPU 时间。2.3 站在维护者的角度别人怎么接手这段代码我特别想强调这一点因为我一个人写得爽和团队能长期接住是完全两码事。DHH 自己是顶级开发者这段代码他自己维护可能没问题但 Once 团队的其他成员要改这个模块怎么办一个普通的 Rust 开发者看到一整个函数由_mm256_*调用组成大概率需要花好几天查指令集手册、反复跑基准测试才敢小心翼翼地动一行。这也是工程倒退最核心的意思性能是团队的整体能力不是某段天才代码的瞬间输出。某个模块的 17 倍提速如果让团队的维护速度降低 10 倍长期来看很可能得不偿失。尤其当项目还处于快速迭代期业务逻辑每两周变一次这种底层优化几乎肯定会成为频繁返工的重灾区。我不反对 DHH 的做法本身我反对的是把能做到和应该做混为一谈。底层优化是一项需要持续投入的专项工作它应该被当作项目里的一个正式课题来管理而不是顺手让 AI 生成一段代码然后合进去就完事。3. 值不值得用我的三条判断标准3.1 命中的场景热点明确、稳定、量级巨大我参与过不少性能优化项目也写过不少 SIMD 代码。判断一个优化到底要不要做我基本只看三个条件是否同时满足。第一个条件是热点足够集中。如果你用 profiler 跑下来发现某个函数占整个系统 CPU 时间的 40% 以上那优化它的收益就很明显。如果最热的函数只占 3%你就算把它提速 17 倍整体收益也就从 100 变成 97.6几乎感知不到。很多人在性能优化上翻车不是优化错了而是优化了一个根本不热的地方。第二个条件是逻辑高度稳定。这个函数不会因为业务迭代频繁变化。典型的例子是基础编解码、哈希计算、加密原语、序列化和反序列化、图像缩放。这类逻辑的商业含义早就固定了十年都不会变值得投入人力去做底层优化。第三个条件是性能确实是硬指标。比如流媒体转码、高频交易、网络包处理、游戏引擎渲染在这些场景里性能直接决定产品能不能上线17 倍是可以改变产品形态的收益。如果只是后台管理页面多等 50 毫秒用户根本感知不到优化它的工程价值就非常有限。3.2 不命中的场景业务逻辑频繁变化、需要跨平台、团队水平不均反过来下面这几种场景我强烈建议不要碰这种优化。业务逻辑频繁变化的模块不要碰。你花两周写好的汇编版本业务方下个月说格式改了你要么痛苦地重写一遍要么眼睁睁看着它失效。这种事我经历过后来我给自己定了个规矩如果这个模块过去半年内改过两次以上就绝对不做指令级优化。需要覆盖多平台的产品不要碰。桌面应用、移动端 App、云服务要同时跑在 x86 和 ARM 上你写一套 x86_64 intrinsic相当于为 ARM 用户准备了一份完全不同的代码路径。两个平台的行为要测试、要对齐、要修 bug所有代价都要乘以二。除非你能像某些基础库那样专门维护多套平台实现否则不值得。团队里只有你一个人懂底层优化的也不要碰。这不是说团队水平差而是指你需要考虑如果我不在了这段代码会怎样。我见过很多项目核心性能代码由某一位大神维护别人连碰都不敢碰。一旦大神离职或者转岗整个团队直接进入怕触碰禁区的僵尸模式。相比之下一段慢 30% 但人人能维护的代码长期价值往往更高。3.3 我自己实际用过的验证流程含步骤和命令如果你的场景确实命中了 3.1 的条件这里分享一条我操作过很多次的流程可以帮你在投入重写之前先判断收益壁垒。第一步用 profiler 确认热点。Linux 下我常用perf简单粗暴perf record -g ./your_program perf report重点看self占比最高的函数用-g生成调用栈确认热点确实集中在该函数上而不是被上层分摊。第二步建立可复现的基准。我推荐用criterion这个 Rust 基准测试库它对统计误差的处理比手写循环靠谱得多。先给原版代码建立基线use criterion::{criterion_group, criterion_main, Criterion}; fn bench_original(c: mut Criterion) { c.bench_function(original, |b| { b.iter(|| original_function(black_box(TEST_DATA))); }); } criterion_group!(benches, bench_original); criterion_main!(benches);跑一次记录下原版的耗时作为后续对比锚点。第三步让大模型生成多个候选方案不要只接受一个答案。你可以要求它分别给出 SIMD、查表优化、内存布局调整、内联汇编四种版本注明每个方案依赖的指令集。这个阶段大模型的输出质量非常关键我会要求它在注释里写明为什么这个版本更快然后用工具验证它的说法。第四步差分验证。这一步不能省。把优化版和原版在大量随机输入上做输出对比fn main() { for i in 0..10000 { let input generate_random_input(i); let a original_function(input); let b optimized_function(input); assert_eq!(a, b, mismatch on input #{}); } }如果发现不一致优先怀疑边界条件越界访问、溢出、未对齐、数据局部性假设错误。这四类问题在指令级优化里出现的频率远超你的想象。第五步在真实负载下跑通才算数。基准函数往往过于理想化真实数据分布、内存状态、多线程竞争都会影响收益。把优化版接回真实程序用perf stat看 CPI、缓存命中率、指令数等指标是否真的改善了。如果只有孤立基准函数快真实程序没快那这优化就没有落地价值。4. 大模型参与底层优化的正确姿势4.1 让大模型生成候选方案而不是直接接受代码我用了大模型辅助编程也有一段时间了踩过最大的坑就是过于信任输出代码的质量。大模型给出的汇编/intrinsic 代码语法可以完全正确逻辑可以完全错误。比如某段_mm256_*排列组合看起来工整实际上字节序搞反了结果在测试集上碰巧和原版一致换一组数据就崩了。所以我的做法是把大模型当顾问不当代理。我会先问它这段代码在 x86_64 下有哪些潜在的优化方向让它列出 SIMD、指令集特化、内存布局、循环变换等不同思路。然后再让它针对每个方向生成具体代码并且要求每一处关键 transformation 都给出解释。这样做的原因是优化思路比优化代码本身更有复用价值。思路是跨平台的这次在 x86 上能用下次换平台还能借鉴代码则是一次性的换平台就废了。我花同样的 token 成本拿到的是可迁移的知识而不是绑死于某块 CPU 的一组指令。4.2 保留 fallback 的工程结构即使你真的要把一段逻辑写成 SIMD/intrinsic 版本我也强烈建议保留一份可读的普通实现并在运行时做指令集检测。Rust 里做这件事很方便#[cfg(target_arch x86_64)] mod simd { use core::arch::x86_64::*; pub fn init() { // 检测当前 CPU 是否支持目标指令集 if is_x86_feature_detected!(avx2) { // 设置全局状态主路径使用 AVX2 版本 } } } fn process(data: [u8]) - Vecu8 { #[cfg(target_arch x86_64)] { if is_x86_feature_detected!(avx2) { return simd::process_fast(data); } } // fallback普通实现 process_simple(data) }这种结构的价值在于普通实现是安全网它保证在任何平台、任何 CPU 上都有可工作的路径SIMD 版本是加速器它在支持的平台上提供额外收益。加速器坏了不至于系统崩溃最差就是退回普通速度。我还建议把普通实现和加速版本长期保留在代码库里不要因为加速版本稳定了就删掉普通版本。普通版本是差分测试的锚点是新人阅读代码的入口也是未来硬件变化时的救生艇。4.3 对性能数字保持警惕基准测试里的常见陷阱最后想专门聊聊性能数字的可信度问题。大模型给你的优化版本跑 bench 可能真的显示 17 倍但这个数字可能充满水分。我见过最典型的情况是基准测试里的输入数据太友好恰好命中优化版本的所有快速路径。比如你的优化版本把某种特殊格式的数据走了 SIMD 快速路径普通数据走了普通路径而基准测试全用那种特殊格式。真实业务里这种格式只占 20%于是整体收益远不及 17 倍。解决方案是基准数据必须从真实负载里采样不能手工构造。第二个陷阱是 CPU 频率波动。现代 CPU 有睿频机制跑基准时前几秒跑在最高频率后面热了降频表现得忽高忽低。我建议基准测试前用工具固定频率Linux 下可以用cpupower frequency-set -g performance或者至少用hyperfine这种支持多次运行并输出置信区间的工具不要只看一次运行结果。第三个陷阱是忽略了整体系统的瓶颈。某个函数从 100 毫秒降到 5 毫秒但如果整个请求是 IO 密集型瓶颈在数据库查询或者网络往返那这 95 毫秒节省下来对端到端延迟几乎没有影响。所以我每次做优化都会先构建一个端到端的性能基线确认这个热点确实在关键路径上再决定是否值得花力气。我最近一次项目里团队引入大模型优化了一段数据编解码代码bench 显示接近 10 倍提升但接进真实服务以后端到端延迟只降低了 2.8%。原因就是那段编解码在整体请求链路里占比很小瓶颈在序列化和数据库访问。后来我们转向优化数据库查询效果立竿见影。这个教训我一直记着永远先优化系统再优化代码。5. 写在最后从 DHH 这个案例里我带走的三点思考我不打算争论 DHH 做得对还是错因为答案完全取决于项目的具体约束和团队的长期目标。但如果今天有人问我在什么情况下我会像 DHH 一样让大模型把 Rust 代码手撕成汇编我的回答是只在性能决定产品生死、热点明确稳定、团队有足够能力接住底层代码的前提下才会考虑。除此之外我更愿意把大模型生成的底层优化当作一种最佳实践启发从里面吸收 SIMD 和指令级优化思路然后用适合维护的 Rust intrinsic 结构落地保留 fallback 和差分测试。另外一点体会是大模型让底层优化的门槛确实降低了。过去写 SIMD 需要熟读指令手册现在你只要能把需求说清楚大模型就能给出可用度很高的候选版本。但它不会替你承担工程责任——正确性验证、跨平台兼容、长期维护这些责任还在你身上而且因为代码变得更底层责任变得更重了。如果你看完这篇也想试试我的建议是从一个小模块开始不要一上来就动核心架构。挑一个稳定、热点集中、安全边界清晰的函数按照 3.3 的流程跑一遍用数据说话再决定是否扩大范围。这样既能享受大模型带来的性能红利又不至于把项目拉进性能卓越但没人敢维护的泥潭。