Loops 深入理解循环(for、while、do-while、foreach、递归等)
如果给定某种情况,您可以对需要使用循环解决的特定事件或函数执行循环,您可以通过任何类型的循环来实现这些。我们如何根据每个循环的速度、效率和内存使用情况来确定它们之间的差异?例如,您有这些循环Loops 深入理解循环(for、while、do-while、foreach、递归等),loops,performance,memory-management,Loops,Performance,Memory Management,如果给定某种情况,您可以对需要使用循环解决的特定事件或函数执行循环,您可以通过任何类型的循环来实现这些。我们如何根据每个循环的速度、效率和内存使用情况来确定它们之间的差异?例如,您有这些循环 for(int i=0;i<10;i++) { array[i] = i+1; } int i = 0; while(i<10) { array[i] = i+1; i++; } for(inti=0;i循环本身的名称给出了用法的概念 当您只需要执行一个操作时,不需要
for(int i=0;i<10;i++) {
array[i] = i+1;
}
int i = 0;
while(i<10) {
array[i] = i+1;
i++;
}
for(inti=0;i循环本身的名称给出了用法的概念
当您只需要执行一个操作时,不需要询问任何问题,for循环就可以很好地执行
如果您在数据结构上进行迭代,并且有一个约束,如中断条件或类似的情况,则应选择while或do-while循环。这只是个人偏好和编码风格的问题-首选的样式也在很大程度上取决于您所用的编码语言
例如,在python中,执行上述循环的首选方法看起来有点像:
for i in range(0, 9):
array[i] = i + 1
(事实上,在Python中,您可以在一行中完成上述操作:
array = range(1,10)
但这只是一个例子……)
在以上两种中,我更喜欢第一种
至于性能,您不太可能看到差异。我将逐一解释每个循环情况
1.For loop:当您确定您执行了某些迭代次数时,请执行For循环
2.While循环:如果不确定迭代次数,则执行While循环或希望循环,直到条件为false
3.执行while:这与while循环相同,只是循环至少执行一次
话虽如此,为另一种情况编写一个循环也是可以的
4.递归:如果您正确理解递归,递归将导致优雅的解决方案。
递归比直接迭代慢一点
for、while和do-while之间没有性能差异。如果有的话,它们可以忽略不计。好吧,这里很难解释循环背后的所有逻辑
为了优化循环,编译器将为您做一些令人惊奇的事情,因此无论您使用while
还是for
,这都无关紧要,因为编译器无论如何都会转换为assembler
为了更深入地理解,您应该学习一些汇编程序,然后学习基本处理器如何工作,如何读取指令以及如何处理指令
为了改进流水线,最好将具有相同变量的语句放在彼此远离的地方。这样,在计算一条语句时,如果下一条语句独立于第一条语句,处理器可以获取下一条语句并开始计算它
例如:
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
时,我们可以开始计算m
和i
。大多数情况下,编译器会检测到这一点并自动执行(称为代码重新排序)。有时对于小循环,编译器会根据需要将代码复制并粘贴到循环中,因为没有控制变量更快
我的建议是,让编译器关注这些事情,并关注您正在使用的算法的成本,最好将O(n!)减少到O(logn),而不是在循环内部进行微优化
根据修改的问题进行更新
好的,依赖项必须是写/写或读/写依赖项。如果是读/读依赖项,则没有问题(因为值不变)。请查看[Data dependency article]()
在您的示例中,这两个代码之间没有区别,m
依赖于c
和b
,但这两个代码从未被写入,因此编译器在进入循环之前就知道它们的值。这称为读/读依赖项,而不是依赖项本身
如果你写过:
...
m=c+GetAvarage(a);
...
然后我们会有一个写/读依赖关系(我们必须在a
中写入,然后从a
中读取,所以我们必须等到a
计算出来),您所做的优化会很好。
但是再一次,编译器为您和其他许多事情做这件事。很难说高级代码中的微优化会对汇编代码产生真正的影响,因为可能编译器已经在为您做这件事,或者可能正在为您重新排序代码,或者可能正在做一千件比w更好的事情我一眼就能想到
但不管怎样,只要知道地毯下的事情是如何运作的就好了:)
更新以添加一些链接
查看以下链接,进一步了解编译器可以做些什么来提高代码性能:
这当然取决于您使用的语言,但请记住,任何代码中最慢的部分都是编写代码的人,因此我建议您为每种情况选择一个标准并坚持执行,然后当您更新代码时,无需每次都考虑这一点
如果你想通过这样的方式节省开支,那么你要么已经在以接近100%的效率运行,要么可能在错误的地方寻找加快代码速度的方法?在帕特森和轩尼诗的《计算机组织与设计:硬件/软件接口》一书中,作者将上述循环转换为汇编,两个循环在MIPS中具有相同的汇编代码
如果您的编译器在不同的汇编语句中编译两个循环,则会出现差异。如果两个循环的性能不相同,则会出现差异。任何合适的编译器都会生成相同的代码
为了测试这一点,我创建了一个名为loops-f.c
的文件:
void f(int array[])
{
for(int i=0; i<10; i++) {
array[i] = i+1;
}
}
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
),然后比较生成的汇编代码()
...
m=c+GetAvarage(a);
...
void f(int array[])
{
for(int i=0; i<10; i++) {
array[i] = i+1;
}
}
void g(int array[])
{
int i = 0;
while(i<10) {
array[i] = i+1;
i++;
}
}
--- 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