现代处理器普遍使用流水线技术(Pipelines)对指令的执行进行了优化。一个指令的完整执行过程被分解为多个环节,交由多个单元进行处理,从而显著提升指令的平均执行效率。然而,流水线技术遇到分支结构时,就遇到了问题。按照哪条分支进行下一步流水线操作呢?纵然有多种分支预测(Branch Predict)策略,但是,对于绝对值函数这样两边概率基本相同,无法预测的情况,当出现分支误预测时,处理器被迫清空流水线,这对指令执行效率产生了较大影响。

Introduction

  根据上面的介绍,绝对值函数的分支结构(如下)产生的分支误预测对流水线产生较大影响。当数据正负随机性较大时,两个分支等可能进入,分支预测基本失效。如果使用较为频繁,此分支结构从处理器层面上对程序效率产生了影响。那么,有没有一种方法可以绕过分支误预测,不对流水线造成影响呢?*最好的办法便是取消分支结构,使程序顺序执行。*那么有没有的操作可以代替分支结构实现功能呢?

  答案是:位运算

Description

  利用位运算和数据存储的一些性质,我们可以在不需要指令判断正负号的情况下将一个数转化为正数。

Float 浮点数

根据IEEE 754标准,浮点数(包括单双精度)的存储方式如下:

double绝对值函数 java double绝对值函数_汇编

  • sign:符号位
  • biased exponent:带偏阶数
  • trailing significand field:尾数

具体细节请参考浮点数Floating-Point Arithmetic其他资料。

根据其存储方式,我们不难发现:正负数仅存在符号位差异,其余部分完全相同

那么非常简单,我们只需要使用位与运算,将符号位强制置0即可

andpd   xmm0, cs:xmmword_1
xmmword_1:	xmmword	7FFFFFFFFFFFFFFFh
  • andpd: 浮点数的位与运算
  • xmmword:长128b的浮点型数据。

以上指令将传入参数的低63b保留,将最高位置0,达到强制转换为正数的目的。

Integer 整数

  对于正数的操作会比浮点数复杂一些。我们知道,整型数据在计算机中的存储方式为补码。将负数转换为正数时,需要取反加一。我们同样可以利用符号位来辅助位运算,代码如下 (待运算参数置于ecx,结果置于eax):

mov		edx, ecx
mov		eax, ecx
sar		edx, 1Fh
xor		eax, edx
sub		eax, edx
retn

表示成表达式形式:x ^ (x >> 0x1f) + (x >> 0x1f)(可能存在优先级问题,请自行区分)。

  • 如果为负数,符号位为1,使用算数右移,将得到0xffffffff,即-1,原数异或相当于取反,再减去这个-1,即为+1,完成取相反数操作
  • 如果为正数,符号位0,算数右移仍为0,与原数异或不变,减0不变,仍为原数

Application

  使用位运算代替分支结构,在浮点数运算中,既减少指令数,又取消分支,执行效率大大提高。即使在正数取绝对值中,指令数增加了,然而顺序指令带来的流水线优化仍大于指令数增多导致的效率损失(笔者按照主流处理器流水线深度推测,没有实际验证,如有错误烦请指出)。

  在gcc编译器的-Ofast选项中,编译器使用了这个优化,将原先的分支结构改成了顺序结构,并进行inline内联优化,进一步提高执行效率。