level 13
循环是程序最常见的结构,而程序运行所花费的时间相当一部分都是花在了各式各样的循环体上,就连评估算法的效率也是以循环为主要时间度量的,因此花点时间来了解对循环的优化还不算是无聊吧?:)
下面这6个例子很简单,说明了提高循环体效率的基本办法是降低循环体的复杂性,因此针对循环体的优化可以大幅度提高程序的效率。
======================================
(总结1) 尽量少用while循环,多用for循环
======================================
{ while循环 }
procedure TMainForm.Btn01Click(Sender: TObject);
var
i, z, T1, T2: Integer;
c: Integer;
begin
for z := 0 to 9 do
begin
T1 := GetTickCount;
c := 0;
i := 0;
while i <= 100000000 do
begin
Inc(c);
Inc(i);
end;
T2 := GetTickCount;
Memo.Lines.Add(IntToStr(T2 - T1));
end;
Memo.Lines.Add('');
end;
进行10次while循环测试所花费的时间(毫秒):
1538
1665
1630
1702
1523
1585
1515
1685
1595
1510
{ for循环 }
procedure TMainForm.Btn02Click(Sender: TObject);
var
i, z, T1, T2: Integer;
c: Integer;
begin
for z := 0 to 9 do
begin
T1 := GetTickCount;
c := 0;
for i := 0 to 100000000 do
Inc(c);
T2 := GetTickCount;
Memo.Lines.Add(IntToStr(T2 - T1));
end;
Memo.Lines.Add('');
end;
进行10次for循环测试所花费的时间(毫秒):
605
645
605
605
628
615
610
670
640
625
我们可以看出while循环花费的时间比for循环多一倍不止,为什么会这样呢?原因就在于循环体的复杂性。我们来看看它们各自的汇编代码:
; while循环
xor eax, eax ; i = 0
;=========循环体============
inc eax ; i++
cmp eax, 05F5E100 ; Compare
jle xxxxxxxx ; <=
;===========================
; for循环
mov eax, 05F5E101 ; i = 100000001
;=========循环体============
dec eax ; i--
jne xxxxxxxx ; !=
;===========================
我们可以看到for的代码明显比while简捷,无论时钟周期和指令长度都比while有优势,所以当循环次数越大时效率差别表现就越明显。
================================
(总结2) 尽量把无关代码移出循环体
================================
一个for循环和一个if判断的代码你会怎么写呢?看看下面两个例子:
{ 逻辑判断在循环内部 }
procedure TMainForm.Btn03Click(Sender: TObject);
var
i, z, T1, T2: Integer;
a, b, c: Integer;
begin
for z := 0 to 9 do
begin
T1 := GetTickCount;
a := 999;
b := 1000;
c := 0;
Inc(a);
for i := 0 to 300000000 do //内部比较
begin
if a > b then
Dec(c)
else
Inc(c);
end;
T2 := GetTickCount;
Memo.Lines.Add(IntToStr(T2 - T1));
end;
Memo.Lines.Add('');
end;
{ 逻辑判断在循环外部 }
procedure TMainForm.Btn04Click(Sender: TObject);
var
i, z, T1, T2: Integer;
a, b, c: Integer;
begin
for z := 0 to 9 do
begin
T1 := GetTickCount;
a := 999;
b := 1000;
c := 0;
Inc(a);
if a > b then //外部比较
begin
for i := 0 to 300000000 do
Dec(c);
end
else
begin
for i := 0 to 300000000 do
Inc(c);
end;
T2 := GetTickCount;
Memo.Lines.Add(IntToStr(T2 - T1));
end;
Memo.Lines.Add('');
end;
让我们来看看测试的结果:
内部比较循环10次测试(毫秒):
3890
3700
3625
3625
3710
3625
3620
3620
3620
3805
外部比较循环10次测试(毫秒):
1836
1805
1810
1810
1810
1810
1810
1810
1810
1810
可以发现,循环内部无判断的函数速度快了一倍。示例Btn03Click的函数比示例Btn04Click多执行了N-1次逻辑判断,并且由于前者老要进行逻辑判断,打断了循环的“流水线”作业,使得编译器不能对循环进行优化处理,从而降低了效率。所以如果循环体内有逻辑判断代码,并且循环次数很大,我们宜将逻辑判断移到循环体的外面。
==============================
(总结3) 大规模循环尽量是内循环
==============================
下面这两个循环表面看起来时间复杂度是一样的,但测试结果是这样的吗?我们来试试:
{ 小规模循环在内 }
procedure TMainForm.Btn05Click(Sender: TObject);
var
i, j, z, T1, T2: Integer;
c: Integer;
begin
for z := 0 to 9 do
begin
T1 := GetTickCount;
c := 0;
for i := 0 to 50000000 do //大规模循环在外
begin
for j := 0 to 5 do
Inc(c);
end;
T2 := GetTickCount;
Memo.Lines.Add(IntToStr(T2 - T1));
end;
Memo.Lines.Add('');
end;
10次测试结果(毫秒):
4426
4540
4315
4320
4320
4320
4320
4320
4320
4315
{ 大规模循环在内 }
procedure TMainForm.Btn06Click(Sender: TObject);
var
i, j, z, T1, T2: Integer;
c: Integer;
begin
for z := 0 to 9 do
begin
T1 := GetTickCount;
c := 0;
for j := 0 to 5 do //小规模循环在外
begin
for i := 0 to 50000000 do
Inc(c);
end;
T2 := GetTickCount;
Memo.Lines.Add(IntToStr(T2 - T1));
end;
Memo.Lines.Add('');
end;
10次测试结果(毫秒):
2427
2405
2410
2410
2410
2410
2405
2410
2410
2410
几乎快了一倍,差别如此之大,有点不相信自己的眼睛吧?原因就在于CPU跨切循环层是需要花费时间的。记住在多重循环中,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数,从而可以大幅度提高循环的效率。
2013年12月22日 12点12分