如果只允许给写了三五年代码、感觉遇到瓶颈的工程师推荐一场演讲,我会毫不犹豫地推这场:Mike Acton 在 CppCon 2014 上的《Data-Oriented Design and C++》(面向数据的设计)。
这场演讲出名,是因为它开场就掀桌子:你以为你在「写逻辑」,其实你在「搬数据」;你以为封装、继承、设计模式是「好设计」,其实它们很多时候只是没付过钱的奢侈品。Acton 时任 Insomniac Games(《瑞奇与叮当》《抵抗》系列背后的工作室)引擎负责人,他不是学院派理论家,而是被真实游戏的帧率deadline追着跑了十几年的实战派——他要回答的问题非常朴素:同样的算法,为什么别人的代码比你快十倍?
答案几乎从不在算法本身,而在数据怎么摆、缓存怎么用、硬件怎么想。这场演讲十年后仍在被反复引用,因为它讲的不是C++技巧,而是一套被大多数程序员跳过的基本功:理解硬件的真实成本。哪怕你不写C++,甚至不写游戏,这堂课都值得你花一个小时。
| 项目 | 内容 |
|---|---|
| 视频标题 | Data-Oriented Design and C++(面向数据的设计与C++) |
| 频道 | CppCon(C++ 大会官方频道) |
| 演讲人 | Mike Acton(时任 Insomniac Games 引擎负责人) |
| 发布时间 | 2014年(CppCon 首届大会) |
| 时长 | 约1小时 |
| 播放量 | 数十万级(官方频道历年累计,以频道页面实际数据为准) |
| 核心主题 | 面向数据设计(DOD):硬件成本、缓存友好与数据布局 |
[视频核心观点] Acton 开场攻击的不是某段代码,而是程序员的三种集体自负:第一,你以为你理解问题,其实你理解的是你对问题的想象;第二,你以为硬件在配合你,其实硬件根本不在乎你的架构漂不漂亮;第三,你把「业界惯例」当成了真理,却从没算过惯例的成本。他明确提出一个尖锐的判断:大多数程序员写出的慢代码,不是输在智力,而是输在先验地拒绝理解机器。
[延伸分析] 这段批评放在今天听反而更扎心。 abstraction 层层堆叠的现代开发环境里,一代工程师可以从没看过一条汇编、没算过一次缓存命中就交付产品——多数场景下这没问题,问题出在热点路径上:高频交易、游戏帧循环、数据库内核、网卡驱动,这些地方的规则完全不同。这很像通信行业的常识:优化空口之前先看信道,信噪比是物理给定的天花板,方案再花哨也只能在天花板下做事。Acton 的核心主张用一句话说就是:软件工程师的第一课,是承认自己活在物理约束里。
[视频核心观点] 演讲给出了贯穿全场的一句话定义:软件的本质是数据变换(data transformation)——输入、变换、输出。程序不是「对象的社区」,不是「类的艺术作品」,它就是一连串状态与状态的转换。所以设计软件的正确顺序是:先彻底理解问题,再把问题翻译成数据模型,最后才轮到代码。代码只是数据模型的「形状」自然长出来的部分。
[延伸分析] 这个视角的威力在于它把「设计」重新排序了。多数人拿到需求后的第一反应是建模类图——找名词、建对象、套设计模式;而 DOD 的顺序是先问三个问题:数据的体量有多大?访问模式是什么(谁在读、谁在写、按什么顺序)?每帧/每次请求的预算是多少纳秒? 这三问之后,很多「标准设计」会自己垮掉。有意思的是,这个思路在高性能领域早有共识:数据库设计者先想页与索引,HPC 研究者先想内存布局,编译器作者先想中间表示。Acton 做的事,是把这个「内核工程师的常识」搬到了普通应用开发者面前。
[视频核心观点] 演讲中最著名的一幕,是那张硬件成本表。以2013年前后的机器为参照,量级大致是:访问一级(L1)缓存约0.5纳秒,分支预测失败约5纳秒,跨到主内存约100纳秒,机械硬盘寻道约10毫秒。Acton 当场问观众「你这段代码大概多快」,几乎没人答得上来。他的观点:没背过这张表的工程师,等于不知道自己做的东西成本几何。
[延伸分析] 这张表后来演化成网上广为流传的「每个程序员都该知道的延迟数字」(Latency Numbers Every Programmer Should Know)。看它的正确方式不是记数字,而是记比例:主存比L1慢约两个数量级,机械盘又比主存慢约五个数量级——也就是说,一次缓存不命中再补上,和一次磁盘IO的差距,大约等于「走路去便利店」和「徒步横穿大陆」的差距。具体数字每年都在变(SSD、NVMe、新缓存层次),但比例结构十年未变。这也解释了为什么一切高性能系统的第一原则都是:能不碰主存就不碰主存,能把数据搬到离计算近的地方就搬。 顺带一提,这个「搬数据比做计算贵」的直觉,日后会成为理解GPU和AI推理的钥匙,这是 Acton 当年未必料到的。
[视频核心观点] 进入正题后,Acton 讲了缓存工作的基本单元:缓存行(cache line),典型大小64字节。CPU不按变量取数,而是整块搬。于是两条铁律浮现:其一,顺序访问是免费的——硬件预取器(prefetcher)会提前把后面的数据搬进缓存;其二,指针追逐是毒药——每跳转一次指针,就可能触发一次缓存不命中,预取器完全帮不上忙。由此引出全场最实用的工具:AOS(Array of Structs,结构数组)与 SOA(Struct of Arrays,数组结构)之争——按「遍历方式」而不是按「业务对象」来组织内存。
[延伸分析] 举个视频里那类例子:你有一百万个敌人对象,每帧只更新其中两个浮点字段。若按传统 OOP 写法 vector<Enemy>(每个 Enemy 内嵌几十个字段),每读2个字段就要陪跑62字节无关数据,有效带宽利用率不到4%;改用 SOA 把「每帧要用的字段」单独抽成连续数组,同样的循环瞬间变成带宽友好型——没改一行算法,速度却换了一个量级。配套手段还有热冷分离(hot/cold splitting):把每次访问的字段和偶尔才碰的字段拆进不同结构。这套思想最漂亮的「远亲」是列式数据库: analytical 数据库(如 ClickHouse 这类)把表按列存储,本质上就是数据库级的 SOA。你在游戏引擎和数仓里会看到同一个主意,因为它们对抗的是同一个物理定律。
[视频核心观点] Acton 接着拆解了两个被低估的成本。分支(if/switch):现代CPU靠流水线和推测执行榨性能,一旦分支预测失败,整条流水线作废重填,代价约在个位数十纳秒量级——看着小,在每秒百万次的内层循环里就是大头。虚函数:一次虚调用意味着一次间接跳转,地址运行期才知道,既打乱分支预测,又阻断内联优化——编译器看见的不再是确定的函数,而是一个谜。
[延伸分析] 这一节常被误读成「别用虚函数」,其实 Acton 的原意更精确:默认值不等于免费。虚函数、动态分配、异常,这些机制的 trouble 不在「用了」,而在「无意识地用在最内层循环里」。工程上对应的解法也成熟:把多态从热循环里提出来(比如先按类型分桶,再对每桶跑无分支的紧循环);用查表或算术替代小分支(所谓 branchless 写法);数据结构上用连续数组加索引替代指针图。补充一个演讲十年后的注脚:这些原则在今天不仅没过时,反而更硬了——SIMD 指令集要求规则、连续、无分支的数据流,GPU 的 warp 调度更是把「同一分支走向」从优化建议变成了硬性要求。当年是性能技巧,如今是并行时代的入场券。
[视频核心观点] 演讲中有个漂亮的段落,Acton 称之为挑战那些从未被挑战的前提(他用了「normalize the impossible」的说法):你真的需要64位浮点存这个坐标吗?真的需要字符串存这个状态吗?真的需要运行时动态分配吗?他举例说,游戏里大量数据完全可以用8位、16位定点数表达——每砍掉一半位宽,同一条缓存行就能多装一倍的有效数据。所谓优化,很多时候不是把代码写快,而是把数据变小。
[延伸分析] 这段在今天读尤其有既视感:AI领域这几年最热的工程方向之一正是量化(quantization)——把大模型的权重从16位浮点压到8位甚至4位,本质和 Acton 说的是同一件事:用「够用的精度」换「翻倍的带宽与缓存收益」。再往深处走一步,这里藏着一条更普适的工程哲学:性能问题常常是需求问题。规格说明书上每一个「顺便加个字段」「就用double吧」的随意决定,都在为未来每一帧的循环埋税。敢于回头质疑需求、给产品经理列出「这个字段的带宽账单」,是高性能工程师区别于普通工程师的分水岭。这不是抠门,这是对物理规律的尊重。
[视频核心观点] 作为一场C++大会演讲,Acton 却对 OOP 传统冷嘲热讽: encapsulation(封装)被当成「好设计」的同义词卖了几十年,但封装本身并不让代码更快或更正确——它只是改变了代码的组织方式。把数据藏进对象再通过getter/setter访问,若因此放弃了控制内存布局的能力,那是在为一种美学付性能税。他同时澄清:他反对的不是抽象,而是从不核算成本的抽象。
[延伸分析] 这里的分寸感值得细品,因为它和「OOP已死」式的流量口号完全不同。Acton 的立场可以概括成一句话:抽象必须交租——每个间接层、每次泛化、每个虚基类,都要能回答「它换来了什么?值多少钱?在热路径上吗?」。答不上来的,默认砍掉。有趣的是,这和「面向对象」概念原作者的本意并不冲突——Kay 那一脉强调的是消息传递与行为封装,而非把内存布局绑死在继承树上;换句话说,被批判的其实是后人发明的「对象即数据+方法的表格」这种惯性写法。对普通开发者的启示很具体:写一个类之前先问,这个类十有八九会以什么顺序被批量遍历? 如果答案是「按某个字段全量扫描」,那它就该按数据布局设计,而不是按语法书上的UML设计。
[视频核心观点] 演讲收尾时,Acton 把希望寄托在听众身上:把硬件成本意识带回各自的团队,先理解问题、再理解数据、最后才写代码。
[延伸分析] 十年过去,这场演讲的影响远超游戏行业。游戏圈长出了 ECS(Entity-Component-System)架构:Unity 推出 DOTS 技术栈,Rust 社区的 Bevy 引擎把「数据导向」写进了设计哲学;Zig 作者 Andrew Kelley 2021年的演讲《Practical Data-Oriented Design》几乎是对 Acton 的隔空致敬,把同一套方法带进了系统编程圈;数据库、网络栈(如内核旁路方案里的预取与批量处理)、乃至大模型推理优化(KV cache 管理、量化、算子融合的核心都是「少搬数据」),处处是它的变体。一场2014年讲硬件缓存的演讲,成了AI时代基础设施的预习材料——这大概是它至今仍在被反复推荐的根本原因:物理定律不会过时,只会换一个行业再收一次学费。
flowchart TD
A["软件 = 数据变换"] --> B["先懂问题"]
A --> C["再懂数据"]
A --> D["最后懂硬件"]
B --> B1["拒绝用想象建模"]
B --> B2["先画数据流"]
C --> C1["AOS 换 SOA"]
C --> C2["热冷字段分离"]
C --> C3["砍位宽砍字段"]
D --> D1["缓存行64字节"]
D --> D2["顺序访问帮预取"]
D --> D3["分支与虚函数有税"]
D1 --> E["L1 纳秒级"]
D1 --> F["主存慢两个数量级"]
D1 --> G["机械盘再慢五个量级"]
E --> H["抽象必须交租"]
F --> H
G --> H
H --> I["成本意识进团队"]
给一线工程师:把「测完再改」变成肌肉记忆。 下次想优化代码时,忍住直接上算法的冲动,先做三件事:找到真正的热循环(用 profiler,不要靠猜);画出这个循环触碰的数据在内存里的真实样子(连续还是散落?每步真正用了几个字节?);对着延迟表估一估每步的成本。多数情况下,把 vector<unique_ptr<T>> 换成连续存放、把冷字段拆出去、把内层分支提出来,这三板斧就能带来一个量级的改善——一行算法都不用动。
给架构师与团队负责人:把成本表贴进评审流程。 DOD 最大的组织学启示是「抽象要交租」可以制度化:设计评审时多问一句「这个接口会在什么体量、什么频率下被调用?单次成本估多少?」,就能拦下大量「顺手多包一层」的隐性债务。往大处看,选型时也多一个尺度:为什么分析型数据库几乎都走向列存?为什么高性能网络栈要批量收发包?它们的答案和 Acton 的是同一张考卷。
给 AI 时代的学习者:这是一把理解 GPU 与大模型的钥匙。 你以后会遇到 GPU 编程、推理优化、量化部署——所有这些领域的底层语言都是「数据怎么搬」。先用这场演讲在 CPU 上建立成本直觉,再去看 GPU 会轻松得多:所谓 SIMT,不过是把「顺序访问、批量处理、别让硬件等你」推到极致的产物。2014年的这一小时,是2026年基础设施工程的最短预习路径。
这场演讲最大的价值,是把一句反直觉的话钉进了工程师的脑子里:代码的速度不是写出来的,是数据摆出来的——先理解硬件的成本,再谈设计的品味,你的代码才配得上你的机器。
本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
