首页 › programming › CPU最怕的弯路:无分支编程深读

CPU最怕的弯路:无分支编程深读

数据说明:本文写作环境为离线沙箱,未能实时抓取 YouTube 页面。文中视频标题、频道与播放量数据来自写作时的公开信息与程序员社区长期流传的引用,均为约数,未经实时核实,请以频道页面为准。文中涉及的体系结构理论、指令集与论文均真实可查,不受此限制。

一、这条视频为什么值得读

在程序员的世界里,有一类视频被称为“常青经典”:它不蹭新闻、不追框架版本,讲的是十年后依然成立的东西,每年都会在新一届程序员中重新翻红一遍。Creel 频道的《Branchless Programming: Why "if" is Sloowww... and How we can Fix It》(中文可译为《无分支编程:为什么 if 这么慢,以及我们如何修复它》)就是这样一条视频。它在技术社区反复被转发,播放量长期攀升,是“分支预测”这个话题在 YouTube 上最常被引用的入门材料之一。

这条视频回答了一个每个写过 if 的人都会震惊的问题:你代码里那句人畜无害的 if,可能正在让你的程序慢上一个数量级——而原因不在代码里,在 CPU 的流水线深处。 更颠覆的是它给出的结论:修复方式往往不是“优化算法”,而是把 if 从代码里删掉,换成看似更笨的算术和位运算。

没看过视频的读者,本文会把这个话题的知识脉络完整展开;看过视频的读者,本文会在视频之外补上三条线:分支预测器的学术源流(Yeh & Patt 的两级自适应预测)、2018 年因为分支预测而爆发的 Spectre 漏洞、以及“编译器其实早就在替你做无分支优化”这个视频里点到为止的细节。

二、视频基本信息

项目 内容
视频标题 Branchless Programming: Why "if" is Sloowww... and How we can Fix It
中文译名 无分支编程:为什么 if 这么慢,以及如何修复它
频道 Creel(YouTube 知识类频道,主讲算法、CPU 体系结构与图形学底层)
发布时间 2021 年(约,据频道页面,未能实时核实)
播放量 约 200 万量级,长期被 Reddit / Hacker News 等社区转发引用(约数,未经实时核实)
时长与形式 十几分钟,以 C++ 编译演示为主:同一逻辑的“有分支版”与“无分支版”逐一对比反汇编与运行耗时
观看方式 在 YouTube 搜索上述英文标题即可找到原片

三、核心主张拆解:视频到底讲了什么

视频的叙事是一个漂亮的“提出问题—拆解机理—给出解法—泼冷水收尾”结构,按顺序可以拆成五个要点。

要点一:if 不是免费的——一条 CPU 指令流水线的“豪赌”

视频的第一个反直觉结论是:if 语句本身不慢,慢的是 CPU 为了执行 if 所做的猜测。

现代 CPU 是流水线式执行的:取指、译码、执行、访存、写回,像工厂流水线一样分成十几级甚至几十级站台。流水线想保持满负荷,就必须提前把后续指令装进流水线。可当 CPU 遇到一条条件跳转(if 编译后的机器码形态)时,它面临一个尴尬:条件要到最后几级站台才能算出来,但前面的站台不能停工等待——停下来就是十几个时钟周期的空洞。

于是 CPU 选择赌:分支预测器预测这条分支“跳”还是“不跳”,然后按预测结果提前执行。赌对了,零损失,好像什么都没发生;赌错了,已经塞进流水线、甚至已经执行了一半的指令全部作废,整条流水线冲刷重来,代价通常在 15~20 个时钟周期量级(现代大核心更深)。视频用反汇编演示了这个过程:一句看似简单的 if (x > 0) sum += x;,在循环里跑几千万次,如果数据模式让 CPU 反复猜错,性能可以差到离谱。

这里有一个关键细节,视频讲得很清楚,但很多转发它的帖子漏掉了:分支预测失败的真实代价不取决于 if 出现的频率,而取决于“可预测性”。如果条件几乎总是同一个结果(比如“文件是否有效”),CPU 会学会它,几乎永不猜错;真正致命的是那种“抛硬币式”的随机分支——比如对无序数组逐个判断正负。数据的排列本身就是性能参数,这一点和直觉完全相悖。

要点二:预测器是怎么“学会”你的习惯的

视频没有深入预测器的内部构造,但这是理解整个话题的核心机制,值得在此补齐。

最朴素的方案是“上次跳了这次还跳”:用一个比特记录每个分支上次的结果。但它有个著名的缺陷——循环结尾那条 i < n 的判断,前 n−1 次都预测“真”,最后一次必然预测错,循环每跑一遍错两次(进入时错一次、退出时错一次)。

于是升级为两级自适应预测器(two-level adaptive branch prediction):每个分支维护自己的“历史寄存器”(最近几次跳或不跳的序列),再用这张历史模式表去索引一个饱和计数器组。相当于 CPU 不只记住“你上次做什么”,还记住了“你最近几次的行为模式是什么”。这套思想由 Yeh 和 Patt 在 1991 年的论文《Two-Level Adaptive Training Branch Prediction》中系统化提出,后来 McFarling 在 1993 年惠普实验室(DEC WRL)的技术报告《Combining Branch Predictors》中把多个预测器(全局历史、局部历史、静态规则)加权融合,成为 gshare 等方案,奠定了此后三十年 CPU 分支预测的基本格局。今天的 Intel、AMD、Apple 芯片里跑的是这些思想的远房后代,预测准确率在普通工作负载下可以做到 95%~99%——但剩下那 1%~5% 的“不可预测分支”,恰恰就是性能深坑。

要点三:删掉 if 的四板斧——视频的核心演示

视频的主体部分是动手改代码。它展示的核心思路是:把“控制流依赖”改写成“数据流依赖”——让结果由数据计算得出,而不是由“跳不跳转”决定。常用的四类手法:

第一斧:条件移动指令(cmov)。 x86 提供 cmov(conditional move)家族指令:数据无条件参与运算,只有结果的选择由标志位决定。max(a, b)、abs(x) 这类逻辑在编译后可以完全不产生跳转。视频用编译器输出对比展示:同一行 x < 0 ? 0 : x,开优化后编译成了 cmov,分支消失,循环耗时断崖式下降。

第二斧:位运算技巧。 经典例子:mask = -(x >= 0) 先把布尔值扩展成全 0 或全 1 的掩码,再用按位与完成“条件选择”。逻辑等价,但每一步都是无条件指令。视频演示了用这类技巧手写无分支的 clamp、符号判断等小函数。

第三斧:查表(LUT)。 把“条件逻辑”预计算成表,运行时用索引直接取结果——用内存换掉分支。这也是视频里强调的思路转换:很多“看起来必须 if”的逻辑,本质上是一个函数,而有限定义域的函数都能变成表。

第四斧:SIMD 向量化。 一条指令同时处理 8 个、16 个数据,而 SIMD 指令集(如 SSE/AVX)里几乎没有条件跳转,只有掩码选择。所以“循环里全是 if”的代码想被自动向量化,第一件事就是去掉分支——这就是为什么无分支写法经常能让编译器自动生成快 8 倍的向量代码。视频用编译器报告和耗时对比把这条链路演示得非常直观。

要点四:编译器不是傻子——它能替你做大部分事

视频一个容易被忽略但极其重要的提醒:你手写的“聪明”无分支代码,编译器可能早就在做;反过来,你手写的看似笨拙的代码,也可能阻止编译器优化。

现代编译器(GCC 的 -O2、Clang 的 -O2)在优化等级开启时会自动做这些事:把简单的 ?: 表达式替换成 cmov;把 countable 的简单循环自动向量化(auto-vectorization);根据 profile-guided optimization(PGO)的运行时数据,把“热路径”代码排在分支的直线方向、把冷路径扔出去。所以视频的立场很克制:无分支编程不是让你重写所有代码,而是在基准测试证明这里是热点、且分支确实不可预测时,用更贴近机器的方式给编译器搭把手。

要点五:泼冷水——"if is slow"是标题党的修辞

视频结尾回归工程师的清醒:绝大多数 if 不需要、也不应该被优化。 分支预测失败一次的代价约为 15~20 个周期,而一次内存访问未命中(cache miss)的代价约 200 个周期;一个每秒执行一万次的配置检查 if,再慢也无人在意。值得动刀的画像非常具体:数据量巨大(百万级以上)、分支条件与数据强耦合且近乎随机、该循环被 perf 剖析证明是热点。三者同时成立,才轮到无分支技术上场。

四、知识与理论溯源

视频背后的每一块砖都有真实出处,这条脉络本身就是一部微型 CPU 体系结构史:

  1. 指令流水线与分支代价:出自 Hennessy 与 Patterson 的经典教材《Computer Architecture: A Quantitative Approach》(《计算机体系结构:量化研究方法》)。该书从第一版起就把“控制依赖导致的流水线停顿”列为核心性能问题之一,书中的流水线章节是视频所有定量结论(约 15~20 周期的预测失败代价)的教科书来源。
  2. 两级自适应分支预测:Yeh & Patt,1991/1992 年发表于 ISCA/HPSCA 的论文《Two-Level Adaptive Training Branch Prediction》。第一次证明“学习分支的历史模式”能大幅超过单比特预测,这是视频里“CPU 会学习你的习惯”的理论源头。
  3. 混合预测器(gshare):McFarling,1993 年,DEC 西部研究实验室技术报告《Combining Branch Predictors》。今天主流 CPU 的预测器几乎都是这一思想的继承者。
  4. 条件移动与分支消除:Intel《64 and IA-32 Architectures Optimization Reference Manual》明确列有“消除可预测性差的分支”优化章节,建议开发者用 cmov、算术替换和查表改写数据依赖分支;独立性能工程权威 Agner Fog 的优化手册(agner.org/optimize)对 cmov 的微架构代价与适用场景有更细的实验数据——包括一个重要警告:cmov 会无条件计算两个候选值,若某个分支本身代价高昂(如除法或潜在缺页),cmov 反而更慢。
  5. 分支预测的反噬——Spectre:2018 年,Kocher 等人发表的《Spectre Attacks: Exploiting Speculative Execution》震惊业界:分支预测与投机执行会把秘密数据遗留在缓存里,即使错误路径被回滚,缓存痕迹抹不掉,攻击者可据此侧信道窃密。这条线视频没有展开,但它是“分支预测是真实且昂贵的工程赌局”的最戏剧性佐证——赌局的副作用大到成了一类漏洞的名字。官方站点 spectreattack.com 保留着论文与演示代码。
  6. SIMD 与分支的天然对立:AVX 等向量指令集用掩码选择替代跳转,这是无分支写法收益经常能放大 8 倍的硬件基础,出处为 Intel/AMD 各自的指令集参考手册。

五、批判性审查:哪些站得住,哪些要打折扣

站得住的部分:分支预测失败的周期代价、cmov 的存在与编译器行为、SIMD 无跳转的特性、查表与掩码技巧的正确性——这些全部有指令集手册、教材和可复现的基准测试背书,属于计算机科学里最扎实的那一类知识。视频的演示方法(同一逻辑两个版本、看反汇编、测耗时)也是标准的性能工程工作流,读者照做能得到一致结论。

需要打折扣的部分,有三处:

其一,"if is sloowww"是修辞,不是结论。视频自己最后也承认这一点,但标题的传播必然把这句话放大。更准确的表述是:“随机性分支在百万级热循环里是慢的”。一篇转发量高的二手解读如果省略了这两个定语,就把一条专业的性能工程建议扭曲成了“if 都要避免”的教条。

其二,演示硬件已过时。视频录制于 2021 年,此后 Apple M 系列等新微架构的预测器深度和内存子系统都有代际进步,视频中出现的具体倍数在今天的新硬件上未必复现;方向不变,数值需自己重测。这也正是性能工程领域“所有基准数据绑定具体机器”的铁律。

其三,视频隐含的语言语境是 C/C++。在 Java、JavaScript、C# 这类运行于 JIT 虚拟机或托管运行时的语言里,你无法直接控制 cmov 的生成,能做的主要是“让数据变得可预测”(如先排序再处理)和“让热分支更稳定”;而 Python 这类解释型语言,瓶颈根本不在分支,这门课整个不适用。把这条视频的结论平移到任何语言,是读者最常犯的过度推广。

另外还有一个视频之外值得一提的争议点:业界对 cmov 的态度在微架构层面并不一致——在预测成功率极高的现实工作负载下,正确预测的分支几乎零成本,而 cmov 恒定付出“双路计算”的成本;Agner Fog 与多个编译器开发者的实测显示,这正是 GCC/Clang 在不同优化等级和启发式下对 cmov 转换态度保守的原因。所以“编译器为什么不把我所有 if 都变成 cmov”有一个很好的答案:因为它大多数时候不该这么做。

六、认知提升与实用启示

把这条视频变成自己能力的一部分,可以落成三条可操作的习惯:

第一,建立“两个周期数”的直觉标尺。 记住两个数量级:分支预测失败 ≈ 15~20 周期,L3/内存未命中 ≈ 数十到 200+ 周期。今后任何人(包括你自己)想在代码评审里争论某处 if 的性能,先问一句:这段代码一天执行几次?分支是硬币还是铁律?90% 的讨论会立刻终结——你省下的不是 CPU 周期,是团队时间。

第二,优化数据排列,而不是先优化代码。 视频最深刻的教益是:数据模式是性能参数。同样的过滤循环,先对数组排序再处理,分支瞬间变成 100% 可预测,可能快出数倍——一行代码没改。遇到热点循环里“不可预测”的条件,第一步考虑的应该是能不能让数据变得规律(排序、分桶、批量同向),其次才是手写无分支版本。

第三,用剖析器说话,用反汇编确认。 养成最小工作流:perf(Linux)或各家 profiler 找热点 → 怀疑分支时看编译器产出的汇编里有没有 cmov/向量指令 → 改写后基准测试对比。这套流程比任何“性能技巧清单”都可靠,也是从“会写代码”到“理解机器”的分水岭。

七、视频论证结构图

下面这张图梳理了整条视频的论证骨架与知识依赖关系:

flowchart TD
    A["反直觉现象: 一句 if 让程序慢数倍"] --> B["机理层: CPU 流水线<br>不能停工, 遇到分支必须提前赌"]
    B --> C["赌局工具: 分支预测器<br>Yeh & Patt 两级自适应 (1991)<br>McFarling 混合预测 gshare (1993)"]
    C --> D{"分支可预测吗?"}
    D -- "可预测 (常见路径/已排序)" --> E["几乎零代价<br>无需优化"]
    D -- "不可预测 (随机数据)" --> F["每次猜错 ~15-20 周期<br>热循环中被放大"]
    F --> G["解法: 把控制流改成数据流"]
    G --> G1["条件移动 cmov"]
    G --> G2["位掩码技巧"]
    G --> G3["查表 LUT"]
    G --> G4["SIMD 掩码向量化<br>收益常放大 8 倍"]
    G1 & G2 & G3 & G4 --> H["现实修正: 编译器 -O2 已自动做大部分<br>cmov 会双路计算, 有反例"]
    H --> I["最终结论: 先 perf 找热点<br>先让数据变得可预测<br>最后才手写无分支"]
    C -. "副作用成为一类漏洞" .-> J["Spectre (2018)<br>投机执行的侧信道泄漏"]

八、结语

这条视频真正教的不是几招位运算戏法,而是一次世界观的校准:你以为程序是代码在运行,其实程序是数据在 CPU 的流水线和缓存里流动,代码只是给这条河流修水渠的方式。 if 快不快,答案不在语法书里,在流水线的赌桌上。理解了这一点,你再读任何性能话题——缓存友好、预取、向量化——都会发现它们是同一件事的不同侧面:别让机器猜,别让机器等。

延伸阅读

  1. Agner Fog《Optimizing software in C++》与《The microarchitecture of Intel, AMD and VIA CPUs》 — 丹麦技术大学性能工程学者的持续更新的免费手册(agner.org/optimize),对 cmov、分支预测、SIMD 的实测细节远比视频深入,是该话题公认的一手参考。
  2. Kocher et al., Spectre Attacks: Exploiting Speculative Execution(2018,spectreattack.com) — 分支预测与投机执行副作用成为安全漏洞的原始论文,本文第五节溯源 5 的完整版。

参考来源

  1. YouTube 搜索:Branchless Programming Creel — 原视频所在频道入口(本文写作环境未联网,未能直接抓取视频页,数据以频道页面为准)
  2. Agner Fog 优化手册 — 分支、cmov 与微架构性能数据的权威免费参考
  3. Spectre Attack 官方页面 — 投机执行侧信道攻击论文与演示代码
  4. Intel 64 and IA-32 Architectures Optimization Reference Manual — 消除低可预测分支的官方优化建议
  5. Computer Architecture: A Quantitative Approach (Hennessy & Patterson) — 流水线与分支代价的教科书出处

本文由「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 等场景全覆盖。

51学通信知识星球海报-扫码加入