3.10 条件分支
条件分支是编程语言中 if-else 语句的底层实现。x86-64 提供了两种实现条件分支的方式:条件跳转和条件传送。
if-else 的汇编实现
基本模式
C 语言的 if-else 语句在汇编中通过 cmp + 条件跳转实现:
// C 源代码
long absdiff(long a, long b) {
long result;
$if (a \ge b) {$
result = a - b;
} else {
result = b - a;
}
return result;
}# 汇编实现(条件跳转版本)
absdiff:
cmpq %rsi, %rdi # 比较 a (%rdi) 和 b (%rsi)
jl .Lelse # 如果 a < b,跳转到 else 分支
$subq %rsi, %rdi # a \ge b: result = a - b$
movq %rdi, %rax
jmp .Ldone
.Lelse:
subq %rdi, %rsi # a < b: result = b - a
movq %rsi, %rax
.Ldone:
ret控制流图
┌──────────┐
│ cmpq a,b │
│ jl .Lelse │
└────┬─────┘
│
┌──────┴──────┐
▼ ▼
┌──────┐ ┌──────┐
│ then │ │ else │
│a-b │ │b-a │
└──┬───┘ └──┬───┘
└──────┬────┘
▼
┌──────┐
│ done │
│ ret │
└──────┘
条件传送指令 cmov
条件传送(conditional move)是一种无分支的条件操作实现,通过数据依赖替代控制依赖。
原理
cmov 指令根据 的状态有条件地执行数据传送:
cmovXX 源操作数, 目的操作数
# 如果条件 XX 为真,则执行:目的操作数 = 源操作数
# 如果条件 XX 为假,则什么也不做(空操作)cmov 指令族
| 指令 | 条件 | 条件码 |
|---|---|---|
cmove (或 cmovz) | 相等 | ZF |
cmovne (或 cmovnz) | 不等 | ~ZF |
cmovs | 负数 | SF |
cmovns | 非负 | ~SF |
cmovl | 小于 (有符号$) | SF ≠ OF |
cmovge | 大于等于 (有符号) | SF = OF |
cmovle | 小于等于 (有符号$) | (SF≠OF) | ZF |
cmovg | 大于 (有符号) | (SF=OF) & ~ZF |
cmovb | 低于 (无符号) | CF |
cmovae | 高于等于 (无符号) | ~CF |
使用 cmov 重写 absdiff
# 条件传送版本(无分支)
absdiff_cmov:
subq %rsi, %rdi #计算 $a - b → %rdi$
subq %rdi, %rsi #计算 $b - a → %rsi$
# 现在 %rdi = a-b, %rsi = b-a
cmpq $0, %rdi # 检查 a-b 的符号 (即 a >= b ?)
cmovl %rsi, %rdi # 如果 a-b < 0 (即 a < b),选择 b-a
movq %rdi, %rax # 返回选中的值
ret条件传送的优缺点
优点:
- 完全无分支,避免分支预测失败惩罚
- 执行时间与条件无关,适合不可预测的分支
缺点:
- 两个分支都会被求值(计算两个结果),增加计算量
- 如果分支包含函数调用、指针解引用或有副作用,不能使用条件传送
// 不适合条件传送的情况(有副作用)
if (condition) {
*ptr = compute(); // 指针解引用
} else {
*ptr = 0;
}性能对比:
| 适用场景 | 条件跳转 | 条件传送 |
|---|---|---|
| 分支高度可预测(如循环边界检查) | 优秀 | 一般 |
| 分支不可预测(如随机数据排序) | 较差 | 优秀 |
| 分支计算量很大 | 较好(只执行一个分支) | 较差(计算两个分支) |
| 分支计算量很小 | 一般 | 较好 |
编译器对分支的优化
条件跳转 条件传送
// 原始 C 代码
long max(long a, long b) {
return (a > b) ? a : b;
}# gcc -O1 条件跳转版本
max_jump:
cmpq %rsi, %rdi
jle .Luse_b
movq %rdi, %rax
ret
.Luse_b:
movq %rsi, %rax
ret# gcc -O2 条件传送版本(优化后)
max_cmov:
cmpq %rsi, %rdi # 比较 a, b
movq %rsi, %rax # rax = b (预先加载 b)
cmovg %rdi, %rax # 如果 a > b, rax = a
ret选择合适优化策略
编译器基于分支预测代价模型决定使用哪种方式:
if (预测难度 × 分支代价 > 两个分支的总代价) {
使用 cmov
} else {
使用 条件跳转
}
set 指令:条件码设置
set 系列指令根据条件码将单字节寄存器设置为 0 或 1。常用于实现布尔表达式。
// C 代码
int less_than(long a, long b) {
return a < b;
}less_than:
cmpq %rsi, %rdi # 比较 a (%rdi) 和 b (%rsi)
setl %al # al = (a < b) ? 1 : 0
movzbq %al, %rax # 零扩展到 64 位
ret常用 set 指令:
| 指令 | 含义 | 示例 |
|---|---|---|
sete D | D = (==) | sete %al |
setne D | D = (!=) | setne %al |
setl D | D = (<, 有符号) | setl %al |
setg D | D = (>, 有符号) | setg %al |
setb D | D = (<, 无符号) | setb %al |
seta D | D = (>, 无符号) | seta %al |
相关笔记
-
- 条件判断的基础
-
- 条件跳转指令详解
-
- 循环中的条件分支
链接到
- 上一个知识点:3.9 跳转指令
- 下一个知识点:3.11 循环的汇编实现
- 相关知识点:4.7 SEQ处理器、4.8 SEQ各阶段分析