实现条件操作的一般逻辑思维应是利用控制的条件转移,即c语言中三目操作符类似 (x>y?x:y)
当条件满足时选择一条路径执行 当条件不满足时则选择另一条路径执行
但是计算机的思维同人类不同
也就是说若使用控制的条件转移
在现代处理器上,它可能会非常低
数据的条件的转移 是一种替代的策略,
条件传送指令 虽然它的使用充满了限制
这些指令会根据条件码的值,选择要么什么都不做要么将一个值复制到寄存器中
下面来看一个例子
int absdiff(int x, int y)
{
return (x < y ? y - x : x -y);
}
来看条件赋值的实现
int cmovdiff(int x, int y)
{
int tval = y-x;
int rval = x-y;
int test = x < y;//可以看到这里计算了条件的结果和两种执行路径的最后结果并重新命名
if(test)
rval = tval;
return rval;
}
上面的两段代码有着相同的功能 下面使用条件赋值的实现
movl 8(%ebp), %ecx
movl 12(%ebp), %edx
movl %edx,%ebx
subl %ecx,%ebx
movl %ecx,%eax
subl %edx, %eax
cmpl %edx,%ecx//这里和上面的代码有着同样的操作计算两种路径的结果 先求值后验证
cmovl %ebx,%eax
基于条件数据传送的代码比基于条件控制转移的代码性能要好,
处理器通过使用流水线以获得高性能,在流水线中一条指令的处理要经过一系列的阶段,每个阶段
执行所需操作的一小部分 通过重叠连续指令的步骤来获得高性能 流水线中充满了待执行的指令
当机器遇到条件跳转时,它还不能够确定是否会进行跳转,(分支预测逻辑)只要猜测的结果可靠那么流水线中会充满指令
若不可靠那么会丢掉它为该跳转指令后所有指令已经做了的工作
然后重新填充流水线
这会导致程序性能的严重下降
控制流不依赖于数据,这使得处理器更容易保持流水线是满的
条件传送指令的两个操作数 :源寄存器或者存储器地址S,和目的寄存器R
(IA32)源值和目的值可以是16位或32位长,不支持单字节的条件传送,汇编器可以从操作数推测出条件传送指令的操作数长度
yu条件跳转不同,处理器可以执行条件传送,而无需预测测试的结果。处理器只是读源值,检查条件码,然后要么更新目的寄存器,要么保持不变
下列是条件传送指令 操作数:S->R
指令 |
同义名 |
传送条件 |
描述 |
Cmove S,R |
Cmovz |
ZF |
相等/零 |
Cmovne S,R |
Cmovnz |
~ZF |
不等/非零 |
|
|
|
|
Cmovs S,R |
|
SF |
负数 |
Cmovns S,R |
|
~SF |
非负数 |
|
|
|
有符号数 |
Cmovg S,R |
Cmovnle |
~(SF^OF)&~ZF |
大于 |
Cmovge S,R |
Cmovnl |
~(SF^OF) |
大于等于 |
Cmovl S,R |
Cmovnge |
SF^OF |
小于 |
Cmovle S,R |
Cmovng |
(SF^OF)|ZF |
小于等于 |
|
|
|
无符号 |
Cmova S,R |
Cmovnbe |
~CF &~ZF |
超过 |
Cmovae S,R |
Cmovnb |
~CF |
超过或相等 |
Cmovb S,R |
Cmovnae |
CF |
低于 |
Cmovbe S,R |
Cmovna |
CF|ZF |
低于或相等 |
总结:条件数据传送提供了一种用条件控制转移来实现条件操作的替代策略,它只能用于很受限制的情况
但是这些情况还是相当常见的,而且利用到了现代处理器的运行方式
小注:有些编译器可能会避免使用这些指令