C / C ++:乘以或比特移位然后除?

在有可能这样做的地方,我想知道用比特移位替换单个乘法后跟整数除法是否更快。 假设我有一个int k,我想将它乘以2.25。

什么更快?

int k = 5; k *= 2.25; std::cout << k << std::endl; 

要么

 int k = 5; k = (k<<1) + (k/4); std::cout << k << std::endl; 

产量

 11 11 

两者都给出了相同的结果,你可以查看这个完整的例子 。

第一次尝试

我定义了函数regularmultiply()bitwisemultiply() ,如下所示:

 int regularmultiply(int j) { return j * 2.25; } int bitwisemultiply(int k) { return (k << 1) + (k >> 2); } 

在使用Instruments进行分析(在2009 Macbook OS X 10.9.2上的XCode中)时,似乎按bitwisemultiply执行比regularmultiply快2 regularmultiply

在此处输入图像描述

汇编代码输出似乎证实了这一点,其中bitwisemultiply将大部分时间花在寄存器重排和函数返回上,而regularmultiply将大部分时间花在乘法上。

regularmultiply

在此处输入图像描述

按位乘法

在此处输入图像描述

但我的审判时间太短了。

第二次尝试

接下来,我尝试用1000万次乘法执行这两个函数,这次将循环放在函数中,这样所有函数的进入和离开都不会掩盖数字。 而这一次,结果是每种方法花费了大约52毫秒的时间。 因此,至少对于相对较大但不是很大的计算次数,这两个函数大约需要相同的时间。 这让我感到惊讶,所以我决定用更长的数字来计算。

第三次尝试

这一次,我只增加了1亿到5亿到2.25,但是按位bitwisemultiply实际上比regularmultiply略慢。

最后的尝试

最后,我切换了两个函数的顺序,只是为了看看Instruments中不断增长的CPU图表是否可能减慢第二个函数的速度。 但仍然, regularmultiply表现稍好一些:

在此处输入图像描述

这是最终程序的样子:

 #include  int main(void) { void regularmultiplyloop(int j); void bitwisemultiplyloop(int k); int i, j, k; j = k = 4; bitwisemultiplyloop(k); regularmultiplyloop(j); return 0; } void regularmultiplyloop(int j) { for(int m = 0; m < 10; m++) { for(int i = 100000000; i < 500000000; i++) { j = i; j *= 2.25; } printf("j: %d\n", j); } } void bitwisemultiplyloop(int k) { for(int m = 0; m < 10; m++) { for(int i = 100000000; i < 500000000; i++) { k = i; k = (k << 1) + (k >> 2); } printf("k: %d\n", k); } } 

结论

那么我们能说些什么呢? 我们可以肯定的一点是,优化编译器比大多数人更好 。 此外,当有大量计算时,这些优化会更加显示出来,这是您真正想要优化的唯一时间。 因此,除非您在汇编中对优化进行编码,否则将乘法更改为位移可能无济于事。

考虑应用程序的效率总是好的,但微效率的提高通常不足以保证使代码的可读性降低。

实际上,这取决于多种因素。 所以我刚刚通过运行和测量时间来检查它。 所以我们感兴趣的字符串只需要很少的CPU指令,这非常快,所以我将它包装到循环中 – 将一个代码的执行时间乘以一个大数字,然后得到k *= 2.25;k = (k<<1) + (k/4);慢约1.5倍k = (k<<1) + (k/4); 。 这是我对comapre的两个代码:

PROG1:

 #include  using namespace std; int main() { int k = 5; for (unsigned long i = 0; i <= 0x2fffffff;i++) k = (k<<1) + (k/4); cout << k << endl; return 0; } 

前卫2:

 #include  using namespace std; int main() { int k = 5; for (unsigned long i = 0; i <= 0x2fffffff;i++) k *= 2.25; cout << k << endl; return 0; } 

Prog1需要8秒,Prog2需要14秒。 因此,通过使用体系结构和编译器运行此测试,您可以获得对您的特定环境正确的结果。

这在很大程度上取决于CPU架构:浮点运算(包括乘法)在许多CPU上变得非常便宜。 但是必要的float-> int转换可能会让你感到困惑:例如,在POWER-CPU上,由于在将值从浮点单元移动到整数单元时生成的管道刷新,常规乘法将会爬行。

在一些CPU(包括我的,这是一个AMD CPU)上,这个版本实际上是最快的:

 k *= 9; k >>= 2; 

因为这些CPU可以在一个周期内进行64位整数乘法运算。 我的版本的其他CPU肯定比你的bitshift版本慢,因为它们的整数乘法不是那么大的优化。 大多数CPU在乘法运算方面并不像以前那么糟糕,但乘法仍然需要超过四个周期。

因此,如果您知道程序将运行哪个CPU,请测量哪个CPU最快。 如果您不知道,您的bitshift版本在任何架构上都不会表现不佳(与常规版本和我的版本不同),这使得它非常安全。

它在很大程度上取决于您使用的硬件。 在现代硬件上,浮点乘法运行速度可能比整数运算速度快,因此您可能希望更改整个算法并开始使用双精度数而不是整数。 如果您正在为现代硬件编写并且您有很多操作,例如乘以2.25,我建议使用双精度而不是整数,如果没有别的办法阻止您这样做。

并且是数据驱动的 – 测量性能,因为它受编译器,硬件和实现算法的方式的影响。