06Machine-LevelProgramming02Control

概述 #

本节主要学习如何控制机器级别指令的执行顺序
基本条件语句(条件码,条件分支)、循环(循环)、switch语句

ProcessorState #

1.条件码 #

  • CF(无符号):进位(溢出)
  • ZF:刚才的计算结果为零
  • SF(有符号):结果的最高有效位为1(负值)
  • OF(有符号):进位(溢出)

这些标志一般情况下会被忽视,当进行条件操作时会被关注

显式设置1 #

显式设置2 #

testq b,a相当于计算 a&b

如果testq a,a。那么只有当a为零时,ZF才会被设置

读取条件码 #

根据条件码的某种组合,将一个字节设置成0或1

  • 最简单的是,设置ZF条件码的值sete %al(ZF是什么就设置成什么)

直接设置寄存器的最低字节 #

该操作不会影响其他7个字节

条件相关例子 #

  • %eax是%rax的低四位
  • mov指令,从单字节到四字节的零扩展指令
  • 前两步骤保证了低四字节的零扩展;x86-64保证:任何计算结果是32位(四字节)的计算,会把寄存器其余32位设置为0(高四字节)。但是,如果是单字节,或双字节,只会影响这个单字节或双字节的结果。

跳转指令 #

包括无条件跳转/有条件跳转

传统分支例子 #

jle .L4 表示如果条件码结果表示是 y <=x,则跳转到 .L4

使用goto表达上述的汇编代码的意思:

goto Else之后,会继续执行Done标签之后的语句,也就是Done标签后的语句是无论判断是否失败都会执行的

编译器翻译 #

Done表示这个C Code后面的所有逻辑代码

条件移动 #

result是最后的结果。先把两个结果都算出来,result默认先赋值给其中一个。如果判断错误,则修改result(有条件的移动),否则直接返回。

  1. 用“提前计算 + 条件选择”取代“分支预测”,避免流水线清空。
  2. 只是 根据条件选择寄存器值,没有跳转,完全避免分支预测错误
  • 假设你知道的指令序列正在代码海洋中巡航,这些指令可以很顺畅的执行,因为他们使用了所谓的流水线技术。这意味着他们在完成下一个指令之前就开始执行下一个指令的一部分,实际上流水线能达到20条以上指令的深度,能达到的深度取决于提前获取的指令的条数,当完成一些指令时仍然有另一些指令留在流水线上,这就是我的海洋线或我的游轮类比。但突然他们到达一个分支会发生什么?他们会试着猜测分支结果,这被称为分支预测技术,猜测会运行哪个分支。在你熟悉的情况下,条件分支将被采用或将被落空,并且他们非常擅长预测,98%的时候他们都能猜对,所以他们甚至可以在路上预测suta曲线,并且开始朝着这个方向前进,只要猜测正确,就会非常有效率。但是,如果分支猜错了,你必须阻止它并转向另一个方向重新开始,在较差的情况下,可能需要40个指令40个时钟周期
  • 通过先执行两个分支来提高效率要容易得多,在最后一分钟,你要做的就是是否将值移入寄存器,这并不需要暂停整个处理器的执行然后重新选择分支执行

ConditionMoveExample #

CMOV是单条指令,不会打断 CPU 的指令流水线。

条件移动的糟糕情形 #

  1. 如果有个分支比较复杂(找素数之类)。条件移动只适用于分支都相对简单直接的计算
  2. 无法使用:有时需要用条件来判断是否可以取消引用指针(如果空指针则不行)
  3. 语句中会产生副作用

2.循环 #

do-while #

统计64位中1的个数

汇编 #

一般情况dowhile的翻译 #

while的翻译1 #

直接跳到test标签:
while语句,在do while之前加了goto test
-O(优化),g表示调试,这是调试用的优化级别。其他情况可能会用-O2

例子1 #

while的翻译2 #

其他的和do-while一样。只加了前两行(如果!Test则跳过循环)

例子2 #

for循环 #

转换为while #

-O1级别下的优化

(编译器的优化:跳过第一个test代码)

3.switch #

常量整数值
没有break则会继续执行(比如case 5)

有点像array[n] ,时间复杂度是1而不是n(线性)

例子–主要代码 #

cmp a,b 表示 进行b-a运算并且设置条件码

“ja"无符号数之间的比较,且指的是"Jump if Above” (如果大于则跳转)

ja表示,如果x-6大于0,跳转到.L8(x>6或x<0,默认行为);否则跳转*.L4(,%rdi,8)进行跳表查找

例子–跳表 #

.L8是默认行为
多次.L7表示他们都是处理同样的行为

例子–各种代码块 #

用到w的时候才初始化 #

其他情况 #

这些跳转表,代码等都是嵌入的,将他们串起来形成文件,由编译器生成

switch语句,编译器怎么处理

无论给哪些值,都必须找出最小值和最大值,如果超出范围,进入默认情况。然后为该范围的值设置表

如果case中最小值为负值,或者不是零,则经常为通过增加/减少偏置值使他变为零

如果只有case0和case 100000,中间的所有值都要建表?
是的,所以你要用if-else语句
如果数值确实稀疏,他会设置一个条件树,可以在对数时间内完成(找出中间数,每次折半处理条件)

总结 #