使用gcc 4.6和-O3,我使用简单的时间命令计时以下四个代码
#include <iostream>
int main(int argc, char* argv[])
{
double val = 1.0;
unsigned int numIterations = 1e7;
for(unsigned int ii = 0;ii < numIterations;++ii) {
val *= 0.999;
}
std::cout<<val<<std::endl;
}
Run Code Online (Sandbox Code Playgroud)
案例1在0.09秒内运行
#include <iostream>
int main(int argc, char* argv[])
{
double val = 1.0;
unsigned int numIterations = 1e8;
for(unsigned int ii = 0;ii < numIterations;++ii) {
val *= 0.999;
}
std::cout<<val<<std::endl;
}
Run Code Online (Sandbox Code Playgroud)
案例2在17.6秒内运行
int main(int argc, char* argv[])
{
double val = 1.0;
unsigned int numIterations = 1e8;
for(unsigned int ii = 0;ii < numIterations;++ii) {
val *= 0.999;
}
}
Run Code Online (Sandbox Code Playgroud)
案例3在0.8秒内运行
#include <iostream>
int main(int argc, char* argv[])
{
double val = 1.0;
unsigned int numIterations = 1e8;
for(unsigned int ii = 0;ii < numIterations;++ii) {
val *= 0.999999;
}
std::cout<<val<<std::endl;
}
Run Code Online (Sandbox Code Playgroud)
案例4在0.8秒内运行
我的问题是为什么第二种情况比其他所有情况都慢得多?案例3显示删除cout会使运行时恢复到预期的状态.案例4表明,更改乘数也会大大减少运行时间.案例2中没有进行哪些优化或优化?为什么?
当我最初运行这些测试时,没有单独的变量numIterations,该值在for循环中被硬编码.一般来说,对这个值进行硬编码会使事情比这里给出的情况慢.对于案例3尤其如此,如上所示几乎立即使用numIterations变量运行,表明James McNellis对整个循环进行优化是正确的.我不确定为什么将1e8硬编码到for循环中会阻止在案例3中删除循环或在其他情况下使事情变慢,但是,案例2的基本前提明显更慢甚至更为真实.
区分装配输出给出了上面给出的情况
案例2和案例1:
movl $ 100000000,16(%esp)
movl $ 10000000,16(%esp)
案例2和案例4:
.long -652835029
.long 1072691150
.long -417264663
.long 1072693245
使用选项-S编译并查看生成的汇编程序输出(名为*.s的文件).
编辑:
在程序3中,循环被删除,因为没有使用结果.
对于情况1,2和4,让我们做一些数学:情况1的结果的基数10对数是1e7*log10(0.999)= -4345(粗略地).对于情况2,我们得到1e8*log10(0.999)= -43451.对于情况4,它是1e8*log10(0.9999)= -4343.结果本身就是pow(10,对数).
浮点单元在x86/x64 cpu上内部使用80位长的双精度数.当数字小于1.9E-4951时,我们得到一个浮点下溢,正如@James Kanze指出的那样.这只发生在案例2中.我不知道为什么这比标准化结果需要的时间更长,也许其他人可以解释.
RenéRichter在下流方面走在了正确的轨道上.最小的正标准化数字约为2.2e-308.在f(n)= 0.999**n的情况下,在大约708,148次迭代之后达到该限制.其余的迭代都停留在非标准化计算中.
这就解释了为什么1亿次迭代所花费的时间略多于执行1000万次所需时间的10倍.前700,000是使用浮点硬件完成的.一旦你击中了非规范化数字,浮点硬件就会出现问题; 乘法是在软件中完成的.
注意,如果重复计算正确计算0.999**N,则不会出现这种情况.最终产品将达到零,从那时起乘法将再次使用浮点硬件完成.这不是发生的事情,因为0.999*(最小非规范化数字)是最小的非规范化数字.持续的产品最终触底.
我们能做的就是改变指数.指数为0.999999将使连续产品保持在标准化数字范围内,达到7.08亿次迭代.利用这个,
Case A : #iterations = 1e7, exponent=0.999, time=0.573692 sec
Case B : #iterations = 1e8, exponent=0.999, time=6.10548 sec
Case C : #iterations = 1e7, exponent=0.999999, time=0.018867 sec
Case D : #iterations = 1e8, exponent=0.999999, time=0.189375 sec
Run Code Online (Sandbox Code Playgroud)
在这里,您可以轻松地看到浮点硬件比软件仿真快多少.