欢迎光临
我们一直在努力

(学习笔记)3.6 控制(3.6.6 用条件传送来实现条件分支)

文章目录

  • 线索栏
  • 笔记栏
    • 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(逆向工程)
  • 总结栏

线索栏

  • 什么是“用条件传送实现条件分支”?它与传统的“用条件控制转移”方法在底层逻辑上有何根本区别?
  • 为什么在现代处理器上,条件传送 (CMOV) 通常比条件跳转 (JMP) 性能更好?这与流水线和分支预测有何关系?
  • 条件传送指令(如图3-18)的后缀命名规则(如 cmovg, cmova)与之前学过的 SET和条件跳转指令有何关联?
  • 如何将C语言的三元条件表达式 v = test-expr ? then-expr :else-expr翻译为基于条件传送的“Goto风格”抽象代码和实际的汇编指令序列?
  • 在什么情况下不能使用条件传送指令?请以 cread函数为例说明。
  • 编译器(如GCC)在“条件跳转”和“条件传送”之间如何做出选择?它主要权衡哪两个因素?

  • 笔记栏

    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 = thenexpr; // 计算then结果
    ve = elseexpr; // 计算else结果
    t = testexpr; // 评估测试条件
    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;
    }

    在这里插入图片描述


    总结栏

    本节揭示了实现条件分支的另一种底层范式,并深入探讨了其在现代处理器架构下的性能意义与适用边界。

  • 范式转变:条件传送代表了从“控制流的选择”(执行哪段代码)到“数据流的选择”(采用哪个值)的转变。它通过消除分支,提供了确定的指令执行流。
  • 性能本质:条件传送的性能优势并非源于指令本身更快,而是源于避免了因分支预测失败而导致的流水线清空巨大惩罚。在分支模式难以预测(p较大)时,此优势显著。
  • 严格的前提:使用条件传送有一个不可违反的强约束:两个结果表达式必须在任何情况下都能安全且无副作用地求值。cread函数是这一约束的经典案例,必须使用分支。
  • 编译器的保守性:尽管有条件传送这一强大工具,但由于无法准确预测量大表达式的计算浪费与分支预测失败惩罚的权衡,GCC等编译器在实际中非常保守,仅对计算简单的表达式使用条件传送。这提示我们,在追求极致性能时,程序员有时需要手动引导编译器生成更优的代码。
  • 最终启示:条件传送是计算机体系结构中“以空间换时间”(计算两个结果,只用一个)和“以确定性换预测性”的经典体现。它深刻揭示了硬件特性(流水线、分支预测)如何直接影响指令集的设计,以及编译器在将高级语言抽象映射到机器代码时所面临的复杂权衡。理解这一节,是后续学习处理器微体系结构(第4、5章)和进行底层性能优化的关键铺垫。

    赞(0)
    未经允许不得转载:171主机测评 » (学习笔记)3.6 控制(3.6.6 用条件传送来实现条件分支)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址