今天看到一个小技巧:
如果一个整数要做2的整数次幂的乘法或除法时,可以通过移位操作来代替,如此,可以提高执行效率。
大抵是这样的:
int i(初始化值);
则计算 i * 4时,可以等效的通过计算 i << 2 来实现,如此可以将执行的效率提升!
话虽如此,程序验证之,代码是在VS2012下编译运行的:
#include <time.h>
#include <iostream>
#define LOOP_NUM 1000000000
int main(int argc, char** argv)
{
clock_t tStart, tEnd;
#pragma region "test multiple"
unsigned int uiMultiplicant = 10;
unsigned int uiMultiplier = 0x04;
unsigned int res = 0;
tStart = clock();
for(int i = 0; i < LOOP_NUM; ++i)
res = uiMultiplicant / 4; //* uiMultiplier;
tEnd = clock();
std::cout << double(tEnd - tStart) / CLOCKS_PER_SEC << std::endl;
uiMultiplier = 0x07;
tStart = clock();
for(int i = 0; i < LOOP_NUM; ++i)
res = uiMultiplicant >> 2;
tEnd = clock();
std::cout << double(tEnd - tStart) / CLOCKS_PER_SEC << std::endl;
#pragma endregion "test multiple"
}
但是经过如上的代码测试,乘(除)操作与相应的左(右)移位操作时间相当。
分析二者的汇编代码,发现除操作和移位操作都解释成了 shr eax, 2这样的移位操作,因此时间相当也是合情合理的了。
再看乘操作和左移操作:
乘操作的汇编为imul eax, ...,是单独的汇编指令,而且经过测试乘操作与相应的左移操作相比似乎效率更高!
所以从VS2012的测试环境来看,移位操作似乎并不能加速乘/除操作,但是这却能给我们一个很好的提示:
如我们要进行一项对其操作,比如要进行4的对齐,一般的写法会是这样的:
int new_len = (old_len + 3)/4 * 4;
为了加速这种运算,我们可以通过如下的技巧:
int new_len = (old_len + 3) & -4;
为何下面的代码能够起到和上面代码一样的效果呢,这就要从数值在计算机中的存储方式来说起了:
数值在计算机中都是以补码的形式存放的,那么何为补码呢?举个简单的例子,对于时钟来说相对于12点(0点),顺时针转8个个格子和逆时针转4个格子会转到相同的位置,那么相对于时钟的12个格子(模为12)来说,-8和+4是互补的。
同样对于一个8位的有符号数来说,它的模为2^7,即128,则-127相应的补码为1。计算机中正数的补码还是它本身,如同时钟一样,正方向转过4个格子就是4个格子,负数的补码为在原码基础上符号位不变,其余位取反,再在最后一位上+1,就像时钟逆转8个格子,用正转4个格子来代替一样!
利用补码的好处就是可以利用硬件的溢出原理,直接进行加操作!
知晓了如上的原理,则4在计算机中的表示为fffffffc,而不是想象中的10000004,那么如上的&操作也便顺理成章!
以上就是个小技巧,不过却可以大大提高效率!