To DelphiGuy:我不是信书的,虽然不是非常明显,但还是能够测试出来。没有去深究具体原因。所以只是当成一种习惯性的写法。
http://www.agner.org/optimize/ 拿i家和a家最新的i7和k10来说: i7中,“Conditional jump”的µops fused domain(The number of µops at the decode, rename, allocate and retirement stages in the pipeline. Fused µops count as one.)是1,而loop的是6,sub/dec的闭着眼也能猜出来肯定是1。在i7上,一条loop顶上正常的3组了。 k10中,Jcc的Ops(Number of macro-operations issued from instruction decoder to schedulers)是1,loop的是7,不用看sub/dec的肯定还是1。在k10上,一条loop比3组还多。而且loop还必须得用ecx寄存器,除了指令长度短点儿没别的好处。不知道你的“快一点点”的依据是什么,测试代码又是什么?loop要真那么管用,为什么现在编译器生成的代码干嘛不用指令长度更短的loop?或者直接贴gcc/icc的优化结果也行,别自己空口无凭的还让别人给证据。
For i:=0 to 10 do
begin
a:=b+i;
d:=c+b;
end;
result:=d;
end;
var a,b,c,i:integer;
add eax,b
ret
asm
mov edx,[edx]
mov ecx,[ecx]
add,ecx,edx
add edx,55
mov [eax],edx
mov eax,ecx
end;
你最好给出完整的函数体。
另外,按照你上面个的代码,无论循环多少次 d 的值都不会变,并且a在赋值之后也没有使用过,为什么要这样?
function Add(i:integer):integer;
var
iOut:integer;
begin
asm
pushad
cmp eax,i
jbe @fun2
mov eax,1
mov ebx,0
@fun1:
add ebx,eax
inc eax
cmp eax,i
jbe @fun1
mov iOut,ebx
jmp @fun3
@fun2:
mov iOut,0
@fun3:
popad
end;
result:=iOut;
end;应该是你想要的。
你先写个for循环,然后下断点,查看cpu,就可以看delphi是怎么写的了
@@loop:
//循环体,在循环体中,不要改变 eax 的值
dec eax
jnz @@loop:
相当于
for i := 0 to 10 do
begin
//循环体
end;
@@loop
只是一个标号啊
后面有dec eax
jnz @@loop
很多书上都这么说,但实际上是一种误解。
loop慢是因为它的吞吐量低,意味着一条loop指令执行之后要过若干时钟周期(典型PIV 2~4,PM 6,Core/Core2 5,i7 4)才能再执行另一个loop指令。这样单独测试loop指令的话肯定是慢的。
不过loop是循环控制指令,实际中不会连续使用的,一般情况下,比较这两种循环:
1:
mov ecx,n
@@loop:
; 一段程序
sub ecx,1 ; add ecx,-1
jnz @@loop2:
mov ecx,n
@@loop:
; 一段程序
loop @@loop只要这一段程序指令数不要太少,2的效率不会比1差,甚至在较新的架构下还要快一点点。
拿i家和a家最新的i7和k10来说:
i7中,“Conditional jump”的µops fused domain(The number of µops at the decode, rename, allocate and retirement stages in the pipeline. Fused µops count as one.)是1,而loop的是6,sub/dec的闭着眼也能猜出来肯定是1。在i7上,一条loop顶上正常的3组了。
k10中,Jcc的Ops(Number of macro-operations issued from instruction decoder to schedulers)是1,loop的是7,不用看sub/dec的肯定还是1。在k10上,一条loop比3组还多。而且loop还必须得用ecx寄存器,除了指令长度短点儿没别的好处。不知道你的“快一点点”的依据是什么,测试代码又是什么?loop要真那么管用,为什么现在编译器生成的代码干嘛不用指令长度更短的loop?或者直接贴gcc/icc的优化结果也行,别自己空口无凭的还让别人给证据。
{$APPTYPE CONSOLE}
program test;uses SysUtils, Windows;const
TestCount = 100000000;
TestLoop = 10; Test1Title = 'SUB/Jcc Loop';
Test2Title = 'LOOP Loop'; ResultMsg1 = 'pass %d: %-0.5f seconds';
ResultMsg2 = 'Average %-0.5f seconds';type
TTestProcedure = procedure;procedure Test1;
asm
mov ecx,TestCount
@@0:
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15; sub ecx,1
jnz @@0
end;procedure Test2;
asm
mov ecx,TestCount
@@0:
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15;
shr eax,15; shr eax,15; shr eax,15; shr eax,15; loop @@0
end;procedure TestProc(const P: TTestProcedure; const Msg: string);
var
T, T1: double;
Ticks: cardinal;
i: integer;
begin
writeln(Msg); T := 0;
for i := 1 to TestLoop do
begin
Ticks := GetTickCount;
P;
Ticks := GetTickCount - Ticks;
T1 := Ticks / 1000.0;
T := T + T1;
writeln(Format(ResultMsg1, [i, T1]));
end;
writeln('-------------------------');
writeln(Format(ResultMsg2, [T / TestLoop]));
writeln;
end;{ main }
begin
TestProc(Test1, Test1Title);
TestProc(Test2, Test2Title);
end.
有一点例外,在Pentium4上,如果将sub ecx,1换成dec ecx,有快一点的倾向。“不要使用loop指令”这种说法,我记得最著名的是出自Abrash的《Graphics Programming Black Book》(中译本名《图形程序开发人员指南》,我在98年买过一本),不过需要注意的是Abrash是针对Pentium及以前的处理器说的,从Pentium以来的处理器变化很大,不能死抱着“真理”不放。以我的经验,intel有一种逐步改进的倾向,在设计指标有富余的情况下,它会每次把以前的慢指令改快一点,积累一段时间,可能就焕然一新了,典型的如idiv指令。至于AMD,我一直不使用它家的产品,缺少发言权。
把您中间的代码都换成: shr eax, 15; db $66, $0f, $1f, $84, $00; dd $00一组12个字节和你的一行代码长度相同,µops/Ops变一半,latency变1/4,throughput在>=k8的amd平台上少一半,在intel的>=p6平台上略减。别说你不知道shr后面那堆东西是干嘛用的。枪药我是没吃过,只不过一向看不惯没证据就瞎说的,或是也不知道自己水平够不够,就敢拿所谓测试结果忽悠人的
SUB/Jcc Loop
pass 1: 0.29700 seconds
pass 2: 0.29600 seconds
pass 3: 0.28100 seconds
pass 4: 0.29600 seconds
pass 5: 0.28100 seconds
pass 6: 0.31200 seconds
pass 7: 0.29700 seconds
pass 8: 0.28000 seconds
pass 9: 0.29700 seconds
pass 10: 0.29600 seconds
-------------------------
Average 0.29330 secondsLOOP Loop
pass 1: 0.71800 seconds
pass 2: 0.70200 seconds
pass 3: 0.70200 seconds
pass 4: 0.70200 seconds
pass 5: 0.70300 seconds
pass 6: 0.71700 seconds
pass 7: 0.70200 seconds
pass 8: 0.70200 seconds
pass 9: 0.71800 seconds
pass 10: 0.70200 seconds
-------------------------
Average 0.70680 seconds
sub/dec+jnz跟36个shr比,总时间只占了1/18,就算能测出差异来也都不到5%了,能看出什么来?实际上由于CPU的乱序执行和分支预测,那两条指令根本一点儿就不占你的所谓调试时间,你那些“几毫秒”的差距根本就是误差。我给的版本同样是12*9字节,只不过指令换了一下,产生的下降幅度我也给出了,loop的能力差一下就显示出来了。所以说,你说那么多都是没用的,您还不如直接说div的速度跟mul一样更有意思一些(给不懂汇编的人看的:实际上div最慢能多花10倍左右的时间)。只要多来些 cache missing,现在CPU的能力保证能把它们测成一样的速度。你以为自己在那死鸭子嘴硬,懂的人就看不出来到底谁对谁错了?况且楼主的代码一共也没几条指令,只要不是miss cache,算下来连改成9条shr的开销都不到,你还在14楼说“效率其实是一样的”,睁眼说瞎话说习惯了?
是您非要歪曲我的意思,树立一个“我的观点”来打击我,事实上,您从一开始的动机和目的就是不端正的,您不要以为别人看不出来。和您斗嘴也没什么意思。这样吧,我为我在17楼的发言负责,您为您在20楼的发言负责,谁没看明白别人的话就跳出来打击别人,或者为保住面子歪曲别人的意思,死鸭子嘴硬,睁眼说瞎话,谁就是TMD 傻X,生孩子没P眼,这样好不好?您满意了吗?:)
这就是为什么严格等价的循环结构,随着循环次数的增加(比如我贴的测试例子,把1亿次循环改成3亿次),出现向着有利于使用LOOP指令的循环倾斜的趋势,尽管优势不明显,但是千万不要草率就断定是“误差”(为什么很容易想明白吧)。