众所周知,除了乘法,除法需要更多的时钟周期。 (请参阅此处的讨论:Floating point division vs floating point multiplication。)
我已经在我的C ++代码中使用x * 0.5
代替x / 2
和x * 0.125
而不是x / 8
,但我想知道我应该采取多远措施。
对于反转时重复出现的小数(即。1 / num
是重复的十进制数),我使用除法而不是乘法(例如x / 2.2
而不是x * 0.45454545454
)。
我的问题是:在迭代次数相当多的循环中,我应该用它们的重复乘法对应物替换除数(即。x * 0.45454545454
而不是x / 2.2
) ,或者这会带来更大的精度损失吗?
编辑:我做了一些分析,我在Visual Studio中启用了完全优化,使用了Windows QueryPerformanceCounter()函数来获取分析结果。
int main() {
init();
int x;
float value = 100002030.0;
start();
for (x = 0; x < 100000000; x++)
value /= 2.2;
printf("Div: %fms, value: %f", getElapsedMilliseconds(), value);
value = 100002030.0;
restart();
for (x = 0; x < 100000000; x++)
value *= 0.45454545454;
printf("\nMult: %fms, value: %f", getElapsedMilliseconds(), value);
scanf_s("");
}
结果为:Div:426.907185ms,值:0.000000 Mult:289.616415ms,值:0.000000
除了优化之外,除法几乎是乘法的两倍。性能优势得到保证,但它们会降低精度吗?
答案 0 :(得分:7)
对于反转时重复出现的小数(即1 / num是重复的小数),我使用除法而不是乘法(例如x / 2.2而不是x * 0.45454545454)。
众所周知,22/10在二进制浮点中无法准确表示,因此您所实现的只是稍微不准确的值,而不是乘以稍微不准确的值。
实际上,如果意图是除以22/10或其他一些不一定在二进制浮点中可以完全表示的实数值,那么一半时间,乘法比除法更准确,因为它巧合的是,1 / X的相对误差小于X的相对误差。
另一个评论是你的微基准测试会遇到次正规数,其中时间不能代表正常浮点数上常规操作的时间,并且在一段时间后,value
为零,再次意味着时间不能代表正常数字乘法和除法的现实。正如Mark Ransom所说,至少应该使两个测量的操作数相同:因为当前写入的所有乘法都采用零操作数并导致零。此外,由于2.2
和0.45454545454
都有类型double
,因此您的基准测试是测量双精度乘法和除法,如果您愿意通过双精度实现单精度除法乘法,这是needs not involve any loss of accuracy(但您必须为1/2.2
提供更多数字。)
但是不要让自己被愚弄试图修复微观基准。 你不需要它,因为当X不能比1 / X更准确地表示时,没有权衡。没有理由不使用乘法。
注意:您应该明确地乘以1 / X
,因为由于两个操作/ X
和* (1 / X)
略有不同,编译器本身无法进行替换。另一方面,您不需要将/ 2
替换为* 0.5
,因为任何有价值的编译器都应该为您执行此操作。
答案 1 :(得分:1)
当乘以倒数与除数时,您将获得不同的答案,但在实践中,它通常无关紧要,并且性能增益是值得的。最多,误差将是1 ULP用于倒数乘法,而½ULP用于除法。但是
a = b * (1.f / 7.f);
而不是
a = b * 0.142857f;
因为前者将为1/7生成最准确的(½ULP)表示。