文章目录
- 线索栏
- 笔记栏
-
- 1. 核心概念
-
- 1)传统方法(条件控制转移)
- 2)条件传送方法
- 3)示例(图3-17,计算绝对值 absdiff)
- 2. 性能优势原理:避免分支预测错误惩罚
-
- 1)现代处理器依赖流水线
- 2)条件跳转的困境
- 3)条件传送的优势
- 4)性能公式
- 3. 条件传送指令详解(图3-18)
-
- 1)格式
- 2)命名与条件
- 3)数据大小
- 4. 通用代码模板
- 5. 条件传送的局限性
- 6. 编译器的权衡策略
-
- 1)GCC的策略
- 2)权衡因素
- 3)结论
- 7. 练习题
-
- 1)练习题3.19
- 2)练习题3.20
- 3)练习题3.21(逆向工程)
- 总结栏
线索栏
笔记栏
1. 核心概念
从“控制流”切换 到 “数据流”选择
1)传统方法(条件控制转移)
利用条件跳转指令,控制CPU执行两个代码块(then和 else)中的一个。这是“控制流”的分岔。
2)条件传送方法
利用条件传送指令,提前计算两个表达式的结果,然后根据条件,选择其中一个值作为最终结果。这是“数据流”的选择。
3)示例(图3-17,计算绝对值 absdiff)

(1)C代码:return (x < y) ? y – x : x – y; (2)条件传送实现(C语言模拟):
long cmovdiff(long x, long y) {
long rval = y – x; // then-expr
long eval = x – y; // else-expr
long ntest = x >= y; // 测试相反条件
if (ntest) rval = eval; // 条件选择
return rval;
}
(3)对应汇编关键指令
movq %rsi, %rax # 计算 rval = y – x (低64位)
subq %rdi, %rax
movq %rdi, %rdx # 计算 eval = x – y
subq %rsi, %rdx
cmpq %rsi, %rdi # 比较 x 和 y
cmovge %rdx, %rax # 如果 x >= y,则 rval = eval
ret
2. 性能优势原理:避免分支预测错误惩罚
1)现代处理器依赖流水线
需要预先确定要执行的指令流以保持高效。
2)条件跳转的困境
处理器遇到条件跳转时,必须预测分支方向。如果预测错误,需要清空流水线,从正确位置重新开始,造成巨大的时间惩罚(T_MP,现代处理器约15-30个周期)。
3)条件传送的优势
指令执行流是确定且线性的,没有分支。处理器无需预测,可以保持流水线充满,避免了预测错误的惩罚。
4)性能公式
(1)平均时间 T_avg§ = T_OK + p * T_MP (2)其中 p是预测错误概率,T_OK是预测正确时的耗时,T_MP是预测错误惩罚。 (3)在 absdiff例子中,测得 T_OK = 8, T_ran = 17.5(p=0.5时),计算得 T_MP = 2*(17.5-8) = 19个周期。而使用条件传送的版本稳定在约8个周期。
3. 条件传送指令详解(图3-18)

1)格式
cmovX S, R。当条件X满足时,将源S(寄存器或内存)的值传送到目的寄存器R。
2)命名与条件
指令后缀X与SET、条件跳转指令完全一致。 (1)l/g/e系列:用于有符号比较(如 cmovl- 小于时传送)。 (2)a/b/e系列:用于无符号比较(如 cmovb- 低于时传送)。
3)数据大小
支持16位(movw)、32位(movl)、64位(movq),不支持单字节。汇编器可从寄存器名推断大小。
4. 通用代码模板
(1)C语言三元表达式:v = test-expr ? then-expr : else-expr; (2)条件传送实现模板:
v = then–expr; // 计算then结果
ve = else–expr; // 计算else结果
t = test–expr; // 评估测试条件
if (!t) v = ve; // 如果条件为假,选择else结果
(3)最后一行由 CMOV指令实现。
5. 条件传送的局限性
(1)核心限制:无论条件真假,then-expr和 else-expr都会被求值。如果任一表达式可能引发错误(如空指针解引用)或产生不可接受的副作用,则不能使用条件传送。 (2)经典反例:cread函数
long cread(long *xp) { return (xp ? *xp : 0); }
(3)非法实现:若用条件传送,会先执行 movq (%rdi), %rax读取 *xp,再判断 xp是否为NULL。当 xp为NULL时,会先触发对空指针的解引用,导致程序崩溃。 (4)正确实现:必须使用条件跳转,先判断 xp是否为空,再决定是否解引用。
6. 编译器的权衡策略
1)GCC的策略
通常只在 then-expr和 else-expr计算量非常小(例如一条算术指令)时,才使用条件传送。
2)权衡因素
(1)浪费的计算:条件传送总是计算两个表达式,如果其中一个计算量很大且经常不被采用,就是浪费。 (2)分支预测错误惩罚:条件跳转在分支模式不可预测时,预测错误惩罚很高。
3)结论
编译器缺乏完美的信息,因此采取保守策略。尽管条件传送在现代CPU上潜力很大,但GCC默认情况下仍更青睐条件跳转。
7. 练习题
1)练习题3.19

2)练习题3.20

A. OP操作:整数除法 /。 B. 工作原理:
leaq 7(%rdi), %rax # 计算 t1 = x + 7
testq %rdi, %rdi # 测试 x 的符号
cmovns %rdi, %rax # 如果 x >= 0 (非负),则 t1 = x
sarq $3, %rax # 结果 = t1 >> 3 (即 t1 / 8)
这段代码实现了 (x<0 ? x+7 : x) >> 3,等价于 x / 8并向零舍入,这是C语言整数除法的语义。 
3)练习题3.21(逆向工程)

(1)关键:通过 testq %rsi, %rsi; jle .L2确定第一个条件是 y > 0。 (2)通过 .L2中的 cmpq $-2, %rsi; cmovle %rdi, %rax确定在 y <= 0时,第二个条件是 y <= -2。 (3)通过 leaq 0(,%rdi,8), %rax确定 val初始值为 8*x。 (4)补全的C代码:
long test(long x, long y) {
long val = 8 * x;
if (y > 0) {
if (x >= y) { // 由 cmpq %rsi, %rdi; cmovge 推出
val = x & y; // 由 andq %rsi, %rdx; cmovge %rdx, %rax 推出
} else {
val = y – x; // 由 subq %rdi, %rax 推出
}
} else if (y <= –2) {
val = x + y; // 由 addq %rsi, %rdi; cmovle %rdi, %rax 推出
}
return val;
}

总结栏
本节揭示了实现条件分支的另一种底层范式,并深入探讨了其在现代处理器架构下的性能意义与适用边界。
最终启示:条件传送是计算机体系结构中“以空间换时间”(计算两个结果,只用一个)和“以确定性换预测性”的经典体现。它深刻揭示了硬件特性(流水线、分支预测)如何直接影响指令集的设计,以及编译器在将高级语言抽象映射到机器代码时所面临的复杂权衡。理解这一节,是后续学习处理器微体系结构(第4、5章)和进行底层性能优化的关键铺垫。




