这里是否可以使用任何循环优化技术来缩短执行时间?我需要使用i和j的嵌套循环,因为我需要(i,j)的组合。
编辑:即使我留下“实际”代码,通过这个简单的任务,这在我的双核盒子上占用了大约5秒,而使用实际代码,它需要大约6秒。我尝试用j + = 0替换fn_val + = 0,它需要~1.73s。这可能是什么原因?
# include <stdio.h>
# include <time.h>
int main(int argc, char **argv)
{
float fn_value=0.0;
int n=10,i,j;
unsigned int k;
clock_t start, end;
start = clock();
for(k=0;k<9765625;k++)
{
for(i=0;i<n;i++)
{
for(j=i;j<n;j++)
// substitute for an "actual" piece of code
fn_value+=0;
}
}
end= clock();
printf("Time taken %lf", (double) (end-start) / CLOCKS_PER_SEC);
return 0;
}
答案 0 :(得分:2)
如果“doStuff”是线程安全的,那么你可能想要查看OpenMP,因为你可以在不同的线程上为不同的i,j和k索引运行doStuff。
答案 1 :(得分:1)
嗯,它可能很好地并行运行。
答案 2 :(得分:1)
你可以循环展开。实际上,您可以为编译器指定一个参数来展开所有这些循环(实际参数取决于您的编译器)。
我不知道你的“实际代码”是什么,能够为你提供更多信息。如果您正在做一些非常重要的事情,那么您希望优化缓存访问。
另外,您是否正在编译优化? (即gcc中的-O3)
根据你的编辑:
“j + = 0”比“fn_val + = 0”快的原因是因为整数arithemtic比浮点运算快得多。
这就是为什么我们需要实际代码为您提供明智的优化。
答案 3 :(得分:1)
循环展开并不总是比编译器能做得更好,正如其他地方所说的那样,分析并找到时间的去处。
我首先关注的是“实际”代码。有没有什么聪明可以用来“阻止”那里的计算?重复使用以前的anwer来便宜地计算下一个等等。
答案 4 :(得分:1)
由于你的最内层循环只有10次迭代,如果你能组合两个内部循环(总共100次迭代),它会稍微提高你的速度。
答案 5 :(得分:0)
循环本身可能无关紧要,这取决于你在最里面的循环中做了多少工作。
您应该进行一些分析,它会告诉您花费了多少时间,并建议可以在哪些方面进行优化。
答案 6 :(得分:0)
这实际上取决于“替代实际代码”的作用以及代码如何使用值i,j和k。如果使用i,j和k,那么实际上可能没有太多(除了多线程,但如果在数学方程中使用,你可能能够使用一些聪明的代数来降低复杂性/重复性)计算)。另一方面,如果没有使用任何值,那么您可以将其设置为一个将执行指定次数的循环(尽管结果可能因编译器/优化级别而异)。
基本上,如果它们是您需要的最小值,则无法优化循环。此外,这种微优化通常会导致许多错误和不可维护的代码(即使在游戏行业,速度至关重要,我们总是优化最后,然后只有最大的瓶颈),你通常会发现它的算法不是代码本身可以优化(或用具有类似结果的更快算法替换)。您给出的示例除了包含以下内容之外不包含实际算法:
fn_value = 0;
k = 9765625;
n = 10;
i = 10;
j = 10;
因此,上面的代码就是你可以替换整个循环的代码,它会尽可能地优化(假设这些值在其他地方使用,否则你可以完全消除它们。)
答案 7 :(得分:0)
很久以前我听过一次......在某些情况下,循环到零可以更快...
所以: -
for(i=0;i<n;i++)
{
for(j=i;j<n;j++)
// substitute for an "actual" piece of code
fn_value+=0;
}
成为(我认为,总是算错;)): -
for(i=n;i--;)
{
for(j=n-i;j--;)
// substitute for an "actual" piece of code
fn_value+=0;
}
当然,你的循环是倒退的......
我很想知道这是否有所作为!我的直觉是你正在优化错误的东西。
啊哈,一个链接: - http://www.abarnett.demon.co.uk/tutorial.html#FASTFOR
答案 8 :(得分:0)
您正在使用浮点代码!编译器是浮点代码的垃圾。
这是我做过的一些测量,我正在使用带有默认优化的DevStudio 2005,我稍微更改了代码:
// added to the inner part of the loop
fn_value += j;
// added a dependancy on fn_value so that the compiler doesn't optimise the
// whole code down to nothing
printf("Time taken %lf - %f", (double) (end-start) / CLOCKS_PER_SEC, fn_value);
所以,我在大约5s内运行。
现在,我稍微更改了代码:
# include <stdio.h>
# include <time.h>
int main(int argc, char **argv)
{
int fn_value=0;
int n=10,i,j;
unsigned int k;
clock_t start, end;
start = clock();
for(k=0;k<9765625;k++)
{
for(i=0;i<n;i++)
{
for(j=i;j<n;j++)
fn_value+=j;
}
}
end= clock();
printf("Time taken %lf - %d", (double) (end-start) / CLOCKS_PER_SEC, fn_value);
return 0;
}
我将fn_value更改为int。现在需要大约一秒钟!因此,添加整数和添加浮点数之间有四秒的开销。然后我用IA32 FPU操作码而不是C代码编写了一个版本,并且得到了大约1.4秒,这比使用整数慢得多。
然后,我使用了C浮点版本,但使fn_value变为double,时间变为1.25s。现在,这让我感到惊讶。它击败了FPU操作码版本,但是,看看解压缩,唯一的区别是纯C版本展开了内循环。
此外,使用浮动时,结果不正确。
这是我的最终测试代码:
# include <stdio.h>
# include <time.h>
void p1 ()
{
double fn_value=0;//if this is a float, the answer is slightly wrong
int n=10,i,j;
unsigned int k;
clock_t start, end;
start = clock();
__asm fldz;
for(k=0;k<9765625;k++)
{
for(i=0;i<n;i++)
{
for(j=i;j<n;j++)
__asm {
fiadd j
}
}
}
__asm fstp fn_value;
end= clock();
printf("p1: Time taken %lf - %lf\n", (double) (end-start) / CLOCKS_PER_SEC, (double) fn_value);
}
void p2 ()
{
double fn_value=0;
int n=10,i,j;
unsigned int k;
clock_t start, end;
start = clock();
for(k=0;k<9765625;k++)
{
for(i=0;i<n;i++)
{
for(j=i;j<n;j++)
fn_value+=j;
}
}
end= clock();
printf("p2: Time taken %lf - %lf\n", (double) (end-start) / CLOCKS_PER_SEC, (double) fn_value);
}
void p3 ()
{
float fn_value=0;
int n=10,i,j;
unsigned int k;
clock_t start, end;
start = clock();
for(k=0;k<9765625;k++)
{
for(i=0;i<n;i++)
{
for(j=i;j<n;j++)
fn_value+=j;
}
}
end= clock();
printf("p3: Time taken %lf - %lf\n", (double) (end-start) / CLOCKS_PER_SEC, (double) fn_value);
}
int main(int argc, char **argv)
{
p1 ();
p2 ();
p3 ();
return 0;
}
总之,double似乎比float更快。但是,我们需要查看该内部循环的内容,以查看转换浮点类型是否会在特定情况下提供任何加速。
<强>更新强>
float版本比其他版本慢的原因是因为float版本不断写入并从内存中读取值。双重和手写版本永远不会将值写入RAM。为什么这样做呢。我能想到的主要原因是降低了操作之间fn_value值的精度。在内部,FPU是80位,而浮点数是32位(在C的这个实现中)。为了使值保持在浮点范围内,编译器通过向/从RAM写入和读取值将80位转换为32位,因为据我所知,没有FPU指令对单个FPU寄存器执行此操作。因此,为了保持数学'32位'(类型为float),它会带来巨大的开销。编译器忽略了80位FPU和64位双精度类型之间的差异,并假设程序员想要尽可能多的类型。