条件执行指令:CSEL / CSINC / CSINV / CSNEG

条件执行,说白了就是让指令自己决定「做不做」。不用跳转,不用打乱流水线。我刚开始接触 ARM64 时,总觉得条件执行是锦上添花,后来在视频编解码项目里被分支预测惩罚搞怕了,才真正体会到它的价值。

CSEL:条件选择

CSEL 是条件执行里最基础的指令。它的语义很简单:如果条件成立,选第一个操作数;否则选第二个。

// 伪代码:if (cond) Rd = Rn; else Rd = Rm;
CSEL X0, X1, X2, EQ   // 如果 Z=1,X0 = X1;否则 X0 = X2

我在做网络协议栈时,经常用 CSEL 替代短小的 if-else。比如根据包类型选择处理函数指针:

// 根据 w0 是否为 0,选择不同处理函数地址
CSEL x3, x1, x2, NE   // w0 != 0 时选 x1,否则选 x2
BR  x3                // 间接跳转
我的习惯:CSEL 适合 2-3 条指令就能搞定的条件赋值。如果分支体内超过 5 条指令,还是老老实实用分支吧。

CSINC:条件选择并递增

CSINC 在 CSEL 基础上多做了一个加法:条件成立时选 Rn,不成立时选 Rm+1。这个指令在边界检查、循环计数场景里特别好用。

// 伪代码:if (cond) Rd = Rn; else Rd = Rm + 1
CSINC X0, X1, X2, EQ

我记得有一次优化字符串处理函数,需要根据比较结果决定是否递增指针。用 CSINC 一条指令搞定,比先 CSEL 再 ADD 省了一条指令,也省了一个临时寄存器。

场景 传统写法 CSINC 写法 指令节省
边界检查+递增 CMP + B.cond + ADD CMP + CSINC 1 条指令 + 1 个标签
最大值计算 CMP + CSEL + ADD CMP + CSINC 1 条指令

CSINV:条件选择并取反

CSINV 在条件不成立时对 Rm 按位取反。这个指令在实现「不等于」逻辑、掩码翻转时非常顺手。

// 伪代码:if (cond) Rd = Rn; else Rd = ~Rm
CSINV X0, X1, X2, GT   // 如果大于,选 X1;否则选 ~X2

嗯,这里要注意:取反是按位取反,不是算术取负。想要算术取负,得用 CSNEG。

我曾经踩过的坑:CSINV 的取反是 ~Rm,不是 -Rm。有一次我把 CSINV 当 CSNEG 用,结果算出来的符号位全反了,调试了半小时才发现。

CSNEG:条件选择并取负

CSNEG 在条件不成立时对 Rm 做算术取负(即 0 - Rm)。这个指令在绝对值计算、符号调整里是神器。

// 伪代码:if (cond) Rd = Rn; else Rd = -Rm
CSNEG X0, X1, X2, LT   // 如果小于,选 X1;否则选 -X2

绝对值函数用 CSNEG 实现,干净利落:

// int64_t abs(int64_t x) 的 ARM64 实现
// x 在 X0,结果放 X0
CMP   X0, #0
CSNEG X0, X0, X0, GE   // x >= 0 时选 x,否则选 -x

条件执行 vs 分支指令:性能对比

为什么我要花这么多篇幅讲条件执行?因为分支指令有代价。现代处理器有深度流水线,分支预测错了就要冲刷流水线,损失 10-20 个周期。

条件执行指令没有分支,没有预测,没有惩罚。代价是什么?条件执行指令的两条路径都要计算,只是最后选一个写回。如果两条路径都很短(1-2 条指令),条件执行完胜。如果某条路径很长,条件执行反而浪费。

对比维度 条件执行 (CSEL 等) 分支指令 (B.cond)
流水线影响 无分支,流水线平滑 预测错误时冲刷 10-20 级
代码密度 高,无标签跳转 低,需要标签和跳转指令
适用分支体大小 ≤ 3 条指令 任意大小
寄存器压力 两条路径都需要准备操作数 只有选中路径需要准备
可预测性 完全确定,无预测 依赖分支预测器
我的经验法则:分支体 ≤ 3 条指令且条件简单 → 用条件执行。分支体 ≥ 5 条指令或条件复杂 → 用分支。中间地带(3-5 条)→ 看分支预测是否稳定,稳定则用分支,否则用条件执行。

你想想看,一个分支预测错误损失 15 个周期,而条件执行最多多算 2-3 条指令。在热点路径上,这个差距就是几倍的性能差异。

条件执行指令决策流程 条件判断点 分支体大小评估 ≤ 3 条指令 3-5 条指令 ≥ 5 条指令 条件执行指令 检查分支预测 稳定→分支 不稳定→条件执行 分支指令 核心原则 分支体越小,条件执行优势越大 分支预测错误率 > 10% 时,优先考虑条件执行 热点路径上,条件执行通常比分支快 2-5 倍

最后说一句:条件执行指令不是银弹。它要求两条路径的操作数都提前准备好,这会增加寄存器压力。我在做数据库引擎优化时,就遇到过寄存器不够用,不得不把条件执行改回分支的情况。工具好不好,关键看你怎么用。