我做了一些速度测试,以找出什么是最快的,当做乘法或除法的数字。我必须非常努力才能打败乐观主义者。我得到了荒谬的结果,比如一个在2微秒内运行的大规模循环,或者乘法和除法的速度相同(如果这是真的话)。
在我终于足够努力地击败了足够多的编译器优化,同时仍然让它优化速度之后,我得到了这些速度结果。他们可能对其他人感兴趣?
如果我的测试仍然有缺陷,让我知道,但善良的看到我只是花了两个小时写这个废话:P
64 time: 3826718 us
32 time: 2476484 us
D(mul) time: 936524 us
D(div) time: 3614857 us
S time: 1506020 us
使用双精度数的“乘除”似乎是最快的除法方法,其次是整数除法。我没有测试除法的准确性。是不是“适当的划分”更准确?我不想在这些速度测试结果之后找出答案,因为我只是在一个以10为基数的常数上使用整数除法,让我的编译器为我优化它;)(也不会破坏它的优化)。
下面是我用来获取结果的代码:
#include <iostream>
int Run(int bla, int div, int add, int minus) {
// these parameters are to force the compiler to not be able to optimise away the
// multiplications and divides :)
long LoopMax = 100000000;
uint32_t Origbla32 = 1000000000;
long i = 0;
uint32_t bla32 = Origbla32;
uint32_t div32 = div;
clock_t Time32 = clock();
for (i = 0; i < LoopMax; i++) {
div32 += add;
div32 -= minus;
bla32 = bla32 / div32;
bla32 += bla;
bla32 = bla32 * div32;
}
Time32 = clock() - Time32;
uint64_t bla64 = bla32;
clock_t Time64 = clock();
uint64_t div64 = div;
for (long i = 0; i < LoopMax; i++) {
div64 += add;
div64 -= minus;
bla64 = bla64 / div64;
bla64 += bla;
bla64 = bla64 * div64;
}
Time64 = clock() - Time64;
double blaDMul = Origbla32;
double multodiv = 1.0 / (double)div;
double multomul = div;
clock_t TimeDMul = clock();
for (i = 0; i < LoopMax; i++) {
multodiv += add;
multomul -= minus;
blaDMul = blaDMul * multodiv;
blaDMul += bla;
blaDMul = blaDMul * multomul;
}
TimeDMul = clock() - TimeDMul;
double blaDDiv = Origbla32;
clock_t TimeDDiv = clock();
for (i = 0; i < LoopMax; i++) {
multodiv += add;
multomul -= minus;
blaDDiv = blaDDiv / multomul;
blaDDiv += bla;
blaDDiv = blaDDiv / multodiv;
}
TimeDDiv = clock() - TimeDDiv;
float blaS = Origbla32;
float divS = div;
clock_t TimeS = clock();
for (i = 0; i < LoopMax; i++) {
divS += add;
divS -= minus;
blaS = blaS / divS;
blaS += bla;
blaS = blaS * divS;
}
TimeS = clock() - TimeS;
printf("64 time: %i us (%i)\n", (int)Time64, (int)bla64);
printf("32 time: %i us (%i)\n", (int)Time32, bla32);
printf("D(mul) time: %i us (%f)\n", (int)TimeDMul, blaDMul);
printf("D(div) time: %i us (%f)\n", (int)TimeDDiv, blaDDiv);
printf("S time: %i us (%f)\n", (int)TimeS, blaS);
return 0;
}
int main(int argc, char* const argv[]) {
Run(0, 10, 0, 0); // adds and minuses 0 so it doesn't affect the math, only kills the opts
return 0;
}
5条答案
按热度按时间k5ifujac1#
有很多种方法来执行某种算术,所以可能不会有一个单一的答案(移位,分数乘法,实际除法,通过对数单位的一些往返,等等;取决于操作数和资源分配,这些可能都具有不同的相对成本)。
让编译器处理它所拥有的程序和数据流信息。
有关适用于x86上的程序集的一些数据,您可以查看:"Instruction latencies and throughput for AMD and Intel x86 processors"
omhiaaxx2#
什么是最快的将完全取决于目标体系结构。这里看起来你只对你碰巧所在的平台感兴趣,从你的执行时间来看,它似乎是64位x86,或者是Intel(Core 2?)或AMD。
也就是说,在许多平台上,浮点数乘以倒数是最快的,但是,正如您所推测的,通常不如浮点数除法精确(两个舍入而不是一个舍入--这是否对您的使用有影响是一个单独的问题)。一般来说,你最好重新安排你的算法,使用更少的除法,而不是跳过一圈又一圈,使除法尽可能高效(最快的除法是你不做的除法),并确保在你花时间优化之前进行基准测试,因为除法瓶颈的算法很少。
此外,如果您有整数源并且需要整数结果,请确保在基准测试中包括整数和浮点数之间的转换成本。
由于您对特定计算机上的计时感兴趣,因此您应该知道Intel现在在其Optimization Reference Manual (pdf)中发布此信息。具体来说,您会对附录C第3.1节“使用寄存器操作数的延迟和吞吐量”中的表格感兴趣。
请注意,整数除法运算的计时在很大程度上取决于所涉及的实际值。根据该指南中的信息,您的计时例程似乎仍然有相当多的开销,因为您测量的性能比与英特尔发布的信息不匹配。
lhcgjxsq3#
正如Stephen提到的,使用optimisation manual-但您还应该考虑使用SSE指令。这些可以在单个指令中执行4或8个除法/乘法。
此外,对于一个划分来说,花费单个时钟周期来处理是相当常见的。结果可能在几个时钟周期内不可用(称为延迟),但是下一次除法可以在这段时间内开始(与第一次除法重叠),只要它不需要第一次除法的结果。这是由于CPU中的管道衬里,就像你可以在之前的负载仍然干燥的情况下洗更多的衣服一样。
乘除是一个常见的技巧,应该在除数不经常变化的情况下使用。
很有可能你会花时间和精力使数学运算变得更快,结果却发现正是内存访问的速度(当你浏览输入和编写输出时)限制了你的最终实现。
kiayqfof4#
我在MSVC2008上编写了一个有缺陷的测试来实现这一点
然后我在32位模式下在AMD64 Turion 64上运行它。我得到的结果如下:
测试有缺陷的原因是使用了volatile,它迫使编译器从内存中重新加载变量,以防变量发生变化。所有这些都表明,在这台机器上的任何实现之间几乎没有什么区别(__int64显然很慢)。
它还明确地表明MSVC编译器执行乘倒数优化。我想GCC也会这样做,如果不是更好的话。如果我改变浮点数和双除法检查除以“i”,那么它会显着增加时间。虽然,虽然很多可能是从磁盘重新加载,但很明显编译器无法轻松优化。
要理解这种微优化,请尝试阅读this pdf.
总而言之,我认为如果你担心这些事情,你显然没有分析你的代码。当问题实际上是问题时,分析并解决问题。
sqserrrh5#
Agner Fog自己做了一些相当详细的测量,可以找到here。如果你真的想优化一些东西,你也应该从他的software optimization resources中阅读其余的文档。
我想指出的是,即使你正在测量非向量化的浮点运算,编译器也有两个选项用于生成的程序集:它可以使用FPU指令(
fadd
,fmul
),或者它可以使用SSE指令,同时仍然每个指令操纵一个浮点值(addss
,mulss
)。根据我的经验,SSE指令更快,不准确性更少,但编译器不会将其作为默认值,因为它可能会破坏与依赖旧行为的代码的兼容性。您可以在gcc中使用-mfpmath=sse
标志来打开它。