关于约翰·卡马克的性能优先思维的实用指南:分析、帧时间预算、权衡取舍,以及如何交付复杂的实时系统。

约翰·卡马克常被当作游戏引擎界的传奇,但真正有用的不是神话,而是可重复的习惯。这不是去模仿某个人的风格或把一切归结为“天才之举”。而是一些实用原则,能在截止期与复杂度叠加时,可靠地带来更快、更流畅的软件。
性能工程就是让软件在真实硬件和真实条件下达到速度目标,同时不破坏正确性。这并不是“无论如何都要变快”。而是一套有纪律的循环:
这种思维在卡马克的工作中屡见不鲜:用数据争论、让改动可解释、偏好可维护的方案。
实时图形无情,因为它每帧都有死线。错过了,用户立刻就感受到卡顿、输入延迟或运动不平滑。其他软件可以靠队列、加载界面或后台任务掩盖低效;渲染器不能协商:要么及时完成,要么就没完成。
这就是为什么这些教训能推广到游戏之外。任何有严格延迟要求的系统——UI、音频、AR/VR、交易、机器人——都能从按预算思考、理解瓶颈、避免突发峰值中受益。
你会得到可应用于自身工作的检查表、启发式规则和决策模式:如何设定帧时间(或延迟)预算,如何先分析再优化,如何选出“那一个要修的点”,以及如何防止回退,让性能成为日常而不是临发布前的恐慌。
卡马克式的性能思考从一个简单转变开始:别把“FPS”当作主要单位,改用帧时间。
FPS 是倒数(“60 FPS”听着好,“55 FPS”听着也差不多),但用户体验由每帧需要多久驱动——更重要的是,这些时间的稳定性。一口气从 16.6 ms 跳到 33.3 ms 是立刻可见的,即便平均 FPS 看起来还不错。
一个实时产品有多个预算,而不仅仅是“渲染更快”:
这些预算会相互影响。用 CPU 密集的批处理来省 GPU 时间可能会适得其反;减少内存占用可能会增加流式或解压的开销。
如果目标是 60 FPS,你的总预算就是 每帧 16.6 ms。一个粗略分配可能是:
如果 CPU 或 GPU 超出预算,就会错帧。这就是团队为什么说“CPU 受限”或“GPU 受限”——不是标签,而是决定下一个毫秒现实来自哪里的方法。
重点不是追逐诸如“在高端 PC 上最高 FPS”这类虚荣指标,而是定义对你的受众什么是足够快——硬件目标、分辨率、电池与热控、输入响应——然后把性能当作可以管理和捍卫的明确预算。
卡马克的默认动作不是“立刻优化”,而是“验证”。实时性能问题充斥着看似合理的猜测——GC 暂停、“慢着色器”、“太多 draw call”——但在你的构建和你的硬件上大多数猜测都是错的。分析是用证据替代直觉的办法。
把性能分析当作一等公民,而不是临时救火工具。捕获帧时间、CPU 与 GPU 时间线,以及能解释它们的计数(三角形、draw call、状态更改、分配、如果能拿到就记录缓存未命中)。目标是回答一个问题:时间到底花在哪儿?
一个有用的模型:在每个慢帧中,总有一件事是限制因子。也许是 GPU 在一个重代价的通道卡住了,或 CPU 在动画更新上卡住,或主线程在同步上停滞。先找到那个约束;其他都是噪声。
有纪律的循环能防止你胡乱折腾:
如果改动没有明显改进,就假定它没帮到忙——因为它很可能无法在下次内容更新时存活。
性能工作特别容易自我欺骗:
先分析能让你的努力更聚焦、权衡更有依据、改动在评审中更容易被辩护。
实时性能问题看起来混乱,因为所有事同时发生:游戏逻辑、渲染、流式、动画、UI、物理。卡马克的本能是扫除噪声,识别当前主导限制——那件设置帧时间的事情。
大多数变慢都落在几个桶里:
关键不是贴标签,而是选择正确的杠杆去撬。
几项快速实验能告诉你谁在掌控:
你很少能通过在十个系统上各减 1% 赢得胜利。找到每帧重复出现的最大开销,先干掉它。移除单个 4 ms 的罪魁往往比数周的微优化更值钱。
当你修好大石头,下一个最大的问题就会显现。这是正常的。把性能工作当作一个循环:测量 → 改动 → 重新测量 → 重新排序。目标不是完美的性能剖面,而是朝着可预测帧时间稳定前进。
平均帧时间看起来可以,但体验仍可能很糟。实时图形是按最糟糕时刻来评判的:爆炸时掉帧、进入新房间的卡顿、打开菜单的顿挫。这就是尾延迟——罕见但频繁到能被注意到的慢帧。
一个大多数时候在 16.6 ms(60 FPS)跑的游戏,但每隔几秒就飙到 60–120 ms,会让人感觉“坏”,即使平均值仍显示 20 ms。人对节奏敏感。一帧的长时间打破输入可预测性、相机运动和视听同步。
尖刺通常来自不均匀分布的工作:
目标是让昂贵工作变得可预测:
别只画平均 FPS 线。记录每帧时间并可视化:
如果你不能解释最差 1% 的帧,那就不能说你真正解释了性能。
当你不再假装能同时拥有一切时,性能工作会变简单。卡马克的风格是让团队把这种权衡说清楚:我们得到什么、付出什么、谁会感受到差别?
大多数决策落在几个轴上:
如果改动提升了一个轴但秘密增加了三个成本,把它记录下来。“这是增加 0.4 ms GPU 和 80 MB VRAM 来换取更柔和的阴影”是可用的说明。“看起来更好”不是。
实时图形不是追求完美,而是稳定达到目标。达成约定阈值:
一旦团队达成比如“在基线 GPU、1080p 下目标是 16.6 ms”的共识,争论就变得具体:这个功能能否让我们保持在预算内,还是必须在别处降级?
不确定时,选可撤销的方案:
可逆性保护进度。你可以先发布安全路径,把激进方案放在开关之后。
避免过度工程化那些看不见的胜利。1% 的平均提升很少值得一个月的复杂化——除非它消除卡顿、修复输入延迟或防止严重内存崩溃。优先处理玩家立刻能感受到的改动,其余的再等等。
当程序是“正确的”时,性能工作会简单许多。相当一部分“优化时间”其实在追踪那些看起来像性能问题的正确性 Bug:意外的 O(N²) 循环、由于标志未重置导致的渲染通道执行两次、逐渐增长的内存泄漏、把竞态变为随机卡顿的条件竞争。
一个稳定、可预测的引擎能让测量干净。如果行为在多次运行中变化,你就不能信任分析,最终会优化噪声。
有纪律的工程实践能加速性能工作:
很多帧时间尖刺是“海森堡式错误”:你加日志或用调试器一看它就消失。解药是确定性复现。
构建小而受控的测试装置:
当卡顿出现时,你要能一键重放 100 次——不是一句模糊的“有时在 10 分钟后发生”。
性能工作受益于小、可审查的改动。大规模重构会同时带来多个失败模式:回归、新分配、隐藏的额外工作。精简的 diff 更容易回答唯一重要的问题:帧时间到底因为什么改变了?
纪律并非官僚主义,而是保持测量可信的方式,使优化变得直接而非迷信。
实时性能不仅关乎“更快的代码”。更重要的是以让 CPU 与 GPU 高效运行的方式组织工作。卡马克反复强调一个简单真理:机器是字面意义上的——它喜欢可预测的数据,讨厌可避免的开销。
现代 CPU 非常快——直到它们在等内存。如果数据散落在许多小对象中,CPU 就在追指针而非做数学运算。
一个有用的比喻:不要为十样东西跑十次购物。把它们放在一个购物车里,一次走完货架。在代码中,这意味着把常用值放在一起(通常是数组或紧凑的结构体),这样每次缓存行拉来都是有用数据。
频繁分配会带来隐藏成本:分配器开销、内存碎片,以及当系统需要整理时的不确定暂停。即便每次分配都“很小”,持续不断也会成为你每帧支付的税。
常见修复是刻意且乏味的:重用缓冲、对象池化、在热点路径使用长期分配。目标不是聪明,而是稳定。
大量帧时间可能消耗在记账工作上:状态更改、draw call、驱动工作、系统调用、线程协调。
批处理是渲染与仿真的“一个大购物车”版本。不要发出许多微小操作,而是成组处理类似工作,减少穿越昂贵边界的次数。经常情况下,减少开销比微调着色器或内层循环更能提升性能——因为机器花更少时间准备工作,更多时间在真正工作上。
性能工作不仅仅是更快的代码——也是更少的代码。复杂性有代价:Bug 更难定位、修复需更细致测试、迭代变慢、回归通过很少用到的路径混入。复杂性还常常带来运行时开销(额外分支、分配、缓存未命中、同步),这些在为时已晚才显现。
一个“巧妙”的系统看起来优雅,直到你到最后期限而某个地图、某个 GPU 或某个设置组合上出现帧峰值。每个额外的特性开关、回退路径和特殊情况都会成倍增加你需要理解和测量的行为数量。复杂性不仅浪费开发者时间,还常在运行时悄悄增加开销。
一个好规则:如果你不能在几句话内向队友解释性能模型,那你很可能无法可靠地优化它。
简单方案有两个优势:
有时最快的路径是删掉功能、裁剪选项或把多个变体合并为一个。更少的功能意味着更少的代码路径、更少的状态组合、减少性能悄然退化的地方。
删掉代码也是质量改进:最好的 Bug 是那个你通过删除生成该 Bug 的模块而消除的 Bug。
补丁(外科式修复)当:
重构(简化结构)当:
简单不是“不够雄心”,而是在压力下选择仍可理解的设计——那时性能最重要。
性能工作只有在你能发现它回退时才会长期保持。这就是性能回归测试的意义:用可复现的方法检测新改动是否让产品变慢、更不流畅或更占内存。
不同于功能测试(回答“它是否工作?”),回归测试回答“感觉速度是否保持不变?”一个构建可以 100% 正确,但如果额外增加 4 ms 帧时间或加载时间翻倍,发布依然可能很糟。
你不需要实验室来开始——只要一致性。
挑一小组基线场景代表真实使用:一个 GPU 重场景、一个 CPU 重场景和一个“最差情况”压力场景。保持它们稳定并脚本化,让相机路径和输入运行一致。
在固定硬件(已知 PC/主机/开发套件)上运行测试。如果你更换驱动、操作系统或频率设置,记录下来。把硬件/软件组合当作测试夹具的一部分。
把结果存入有版本历史的记录:提交哈希、构建配置、机器 ID 和测得指标。目标不是一个完美数字,而是可信的趋势线。
偏向难以争论的指标:
定义简单阈值(例如:p95 帧时间不得回退超过 5%)。
把回退当作有负责人和截止日的 Bug。
首先,二分定位引入它的变更。如果回退阻塞发布,快速回滚并带着修复再落地。
修复后,增加护栏:保留测试、在代码中加注释并记录期望的预算。习惯才是胜利——性能成为维护项,而不是“以后再做”的任务。
“发布”不是一个日历事件——它是工程要求。一个只在实验室良好运行、或只有经过一周手工调优才满足帧时间的系统,还没有完成。卡马克的心态把现实世界的约束(硬件多样性、混乱内容、不可预测的玩家行为)从一开始就当作规范的一部分。
接近发布时,完美不如可预测重要。用明白无误的术语定义不可妥协项:目标 FPS、最差帧时间尖刺、内存上限和加载时间。任何违反这些的都当作 Bug,而不是“打磨”。这把性能工作从可选优化重塑为可靠性工作。
并非所有变慢同等重要。先修复对用户可见度最高的问题:
剖析纪律在这里回本:你不是在猜哪个问题“看起来大”,你是基于测量的影响来选择。
晚期的性能工作风险高,因为“修复”可能带来新成本。采用分阶段发布:先落地插桩,再把改动放在功能开关,最后逐步扩大暴露范围。默认设置优先保证性能——即便会略微降低视觉质量——尤其是自动检测配置时。
如果你发布多个平台或档位,把默认视为产品决策:看起来稍逊色总好过不稳定。
把权衡翻译为可预期的结果:“这个效果在中端 GPU 每帧要多花 2 ms,会让激战时掉到 60 FPS 以下。”提供可选方案而非说教:降低分辨率、简化着色器、限制生成速率或接受更低的目标。把约束表述为带明确用户影响的选择,会更易被接受。
你不需要新引擎或重写来采用卡马克式的性能思维。你需要一个可重复的循环,让性能可见、可测试且不易被意外破坏。
测量: 捕获基线(平均值、p95、最差尖刺)以及关键子系统的帧时间。
预算: 为 CPU 与 GPU(若内存紧张也包括内存)设定每帧预算。把预算写在功能目标旁。
隔离: 在最小场景或测试中复现开销。如果无法复现,就无法可靠修复。
优化: 每次只改一件事。偏好能减少工作的改动,而不仅仅是“让它更快”。
验证: 重新分析、比较差异,并检查质量回归与正确性问题。
文档: 记录改动、为何有效、未来需监控的点。
如果你想在团队内部把这些习惯工具化,关键是减少摩擦:快速实验、可复现的测试装置和便捷回滚。
Koder.ai 可以在构建外围工具时帮忙——不是替代引擎本身。它是一个 vibe-coding 平台,能生成真实、可导出的源码(Web 前端 React;后端 Go + PostgreSQL;移动端 Flutter),让你快速搭建内部仪表盘来展示帧时间百分位、回归历史和“性能评审”清单,并随着需求通过对话迭代。快照与回滚也很契合“改一件事,重新测量”的循环。
如果你想要更多实用指导,可浏览 /blog 或查看团队如何在 /pricing 上将这些工具化。
帧时间是每帧所用时间,以毫秒(ms)为单位,它直接映射到 CPU/GPU 做了多少工作。
选定一个目标(例如 60 FPS),把它转成硬性截点(16.6 ms),然后把这个时间分配成明确的预算。
示例起点:
把这些当作产品需求,根据平台、分辨率、热控和输入延迟目标调整。
先让测试可复现,然后在做任何改动前先测量。
在你知道“时间到底花在哪”之前,不要决定去优化什么。
做一些快速、有针对性的实验来隔离瓶颈:
在你能以毫秒命名主导成本之前,别动手重写系统。
因为用户感受到的是最糟的帧,而不是平均值。
跟踪:
一个平均 16.6 ms 但偶尔飙到 80 ms 的构建仍然会被感知为“坏”的体验。
让昂贵的工作变得可预测并按计划执行:
同时记录尖刺以便重现和修复,而不是寄希望于它们自行消失。
把权衡用数字和用户影响表述出来。
使用像这样的陈述:
然后根据达成的阈值决定:
不确定时,偏向可逆的决定(功能开关、可扩展质量等级)。
因为不稳定的正确性会让性能数据不可信。
实用步骤:
如果行为在运行间变化,你将不得不去优化噪声而非真正的瓶颈。
很多“快代码”工作本质上是内存与开销的优化。
关注:
通常,削减开销比优化内层循环带来更大的收益。
让性能可测、可复现并且不容易被意外破坏。
出现回退时:引入变更,指定负责人,必要时并修复再落地。