如果给你一些情况,你可以做一个需要使用循环解决的特定事件或函数的循环,你可以通过任何类型的循环来实现这些。我们如何根据速度,效率和内存使用情况确定可以使用的每个循环之间的区别?例如,你有这些循环
for(int i=0;i<10;i++) {
array[i] = i+1;
}
int i = 0;
while(i<10) {
array[i] = i+1;
i++;
}
上面的例子有相同的输出,当然你在执行时看不出它们之间的区别,因为这只是一个小过程,如果你正处理一个耗费你记忆的巨大循环怎么办?哪个循环更好用?是否有适当的措施何时使用什么循环?
对于pakore回答,
从你的答案中我可以有把握地说,如果我重新排序我的变量,其中大多数相关的变量彼此相距很远(中间的其他行)可能更有效。比如说,
a=0;
b=1;
c=5;
d=1;
for(int i=0; i<10;i++)
{
a=b*CalculateAge()+d;
m=c+GetAvarage(a);
d++;
}
和
a=0;
b=1;
c=5;
d=1;
for(int i=0; i<10;i++)
{
a=b*CalculateAge()+d;
d++;
m=c+GetAvarage(a);
}
后者比第一行效率更高,因为后者我在第一行调用了外部方法,第二行独立于第一行的结果而不是第三行的结果。
由于第一个示例将在执行第二行之前等待第一行的结果,而第三行的结果将在等待第一行的结果时执行第二行。
优化的循环不会影响您正在使用的循环类型。正如pakore所解释的那样,以及polygenelubricants,你在循环中可以想到的主要问题是你的代码是如何编写的。编译器的工作是优化代码,如果你根据每个变量对每个变量的依赖性来优化代码,它也会有所帮助,如下面的pakore所解释的那样。
答案 0 :(得分:8)
嗯,这里很难解释循环背后的所有逻辑。
为了优化循环,编译器会为你做出惊人的事情,所以如果使用while
或for
并不重要,因为编译器无论如何都会转换为汇编程序。
为了更深入地理解,你应该学习一些汇编程序,然后学习基本处理器的工作原理,它如何读取指令以及它如何处理它们。
为了改善流水线操作,最好将具有相同变量的语句放置在彼此远离的位置。这样,当计算一个语句时,处理器可以接受下一个语句,如果它独立于第一个语句并开始计算它。
例如:
a=0;
b=3;
c=5;
m=8;
i=0;
while(i<10){
a=a+b*c;
b=b*10+a;
m=m*5;
i++;
}
我们在a
和b
之间存在依赖关系,并且这些语句彼此相邻。但我们发现m
和i
与其他人无关,所以我们可以这样做:
a=0;
b=3;
c=5;
m=8;
i=0;
while(i<10){
a=a+b*c;
m=m*5;
i++;
b=b*10+a;
}
因此,在计算a
时,我们可以开始计算m
和i
。大多数情况下,编译器会检测到并自动执行此操作(它是调用代码重新排序)。有时候对于小循环,编译器会根据需要多次复制和粘贴循环中的代码,因为没有控制变量会更快。
我的建议是让编译器关注这些事情并关注你正在使用的算法的成本,最好从 O(n!)减少到 O( logn)而不是在循环内进行微优化。
根据修改后的问题进行更新
嗯,依赖关系必须是写/写或读/写依赖。如果它的读/读依赖性没有问题(因为值不会改变)。查看[数据依赖性文章](http://en.wikipedia.org/wiki/Data_dependency)。
<击>
在您的示例中,两个代码之间没有区别,m
取决于c
和b
,但这两个代码从不写入,因此编译器在进入循环之前知道它们的值。这是一个称为读/读依赖的,它本身不是一个依赖。
如果你写了:
...
m=c+GetAvarage(a);
...
然后我们会有一个写/读依赖(我们必须写a
然后从a
读取,所以我们必须等到{{1} }计算)你做的优化会很好。
击>
但是再一次,编译器会为你和许多其他事情做这件事。很难说高级代码中的微优化会对汇编代码产生真正的影响,因为编译器可能已经为您做了这样的事情,或者可能正在为您重新排序代码,或者可能正在执行其他一千件事情比我们第一眼就能想到的要好。
但无论如何,知道如何在地毯下工作是好的:)
更新以添加一些链接 查看这些链接,以进一步了解编译器可以做些什么来提高代码性能:
答案 1 :(得分:3)
我将逐一解释每个循环案例,
1. For循环:当您确定 某些 没有迭代时,请转到 for 循环。
2. while循环:当您不确定没有迭代时,请转到while循环,或者您希望循环直到条件为false。
3. do-while :这与while循环相同,只是循环执行至少一次。
话虽如此,也可以为另一个案例编写一个循环。
4. 递归:如果您正确理解递归,递归会带来优雅的解决方案。 递归比直接迭代慢一点。
for,while,do-while之间没有性能差异。如果有的话,它们可以忽略不计。
答案 2 :(得分:2)
你应该写出最自然,惯用和可读的代码,清楚地传达你的意图。在大多数情况下,没有任何一个循环比另一个循环表现更好,以至于你牺牲了上述任何一个,但速度很快。
大多数主流语言的现代编译器在优化代码方面非常聪明,并且可以特别针对人们应该编写的各种可读代码。代码越复杂,人类就越难理解,编译器就越难以优化。
大多数编译器可以优化tail recursion,允许您递归地表达算法(在某些情况下这是最自然的形式),但实际上是迭代地执行它。否则,递归可能比迭代解决方案慢,但在进行此优化之前应考虑所有因素。
如果可以快速编写一个工作正确但可能稍微慢一点的递归解决方案,那么它通常更适合复杂的迭代解决方案,该解决方案可能更快但可能不是很明显正确和/或难以维护。
不要过早优化。
答案 3 :(得分:1)
任何体面的编译器都会生成相同的代码。
为了测试这个,我创建了一个名为loops-f.c
的文件:
void f(int array[])
{
for(int i=0; i<10; i++) {
array[i] = i+1;
}
}
和名为loops-g.c
的文件:
void g(int array[])
{
int i = 0;
while(i<10) {
array[i] = i+1;
i++;
}
}
我将文件编译为程序集(gcc -std=c99 -S loops-f.c loops-g.c
),然后我比较了生成的汇编代码(diff -u loops-f.s loops-g.s
):
--- loops-f.s 2010-08-06 10:57:11.377196516 +0300
+++ loops-g.s 2010-08-06 10:57:11.389197986 +0300
@@ -1,8 +1,8 @@
- .file "loops-f.c"
+ .file "loops-g.c"
.text
-.globl f
- .type f, @function
-f:
+.globl g
+ .type g, @function
+g:
.LFB0:
.cfi_startproc
pushq %rbp
@@ -30,6 +30,6 @@
ret
.cfi_endproc
.LFE0:
- .size f, .-f
+ .size g, .-g
.ident "GCC: (GNU) 4.4.4 20100630 (Red Hat 4.4.4-10)"
.section .note.GNU-stack,"",@progbits
正如您所看到的,代码几乎完全相同。
答案 4 :(得分:0)
循环的名称本身就可以了解用法。
当你只需要执行一个操作时,没有问题,for循环就可以了。
如果你正在迭代数据结构并且有一个约束,比如一个中断条件或类似的东西,应该选择while或do while循环。
答案 5 :(得分:0)
这只是个人偏好和编码风格的问题 - 首选风格也很大程度上取决于您编写的语言。
例如在python中,执行上述循环的首选方法看起来有点像:
for i in range(0, 9):
array[i] = i + 1
(实际上在Python中,您可以在一行中完成上述操作:
array = range(1,10)
但它只是一个例子......)
在上述两个中,我倾向于使用第一个。
至于表现,你不太可能看到差异。
答案 6 :(得分:0)
它肯定取决于您使用的语言,但请记住,任何代码中最慢的部分是编码它的人,所以我建议为每种情况选择一个标准并坚持下去,然后当你来更新时代码你不需要每次都考虑这个。
如果您试图通过这种方式节省成本,那么您要么已经以接近100%的效率运行,要么可能在错误的位置加快代码?
答案 7 :(得分:0)
在Patterson和Hennessy的作者“计算机组织和设计:硬件/软件接口”一书中,将上面的循环转换为汇编,两个循环在MIPS中具有相同的汇编代码。
如果编译器在不同的汇编语句中编译两个循环,如果它们具有相同的性能,则会出现差异。