Delphi - Copy函数效率的问题
技术交流,DH讲解.
最近和肥鸟交流了下关于字符串方面的知识,而这篇文章是很久以前写的,现在发出来吧.
我们写两段代码来对比下:
第一个用Copy函数:
procedure TForm1.Button1Click(Sender: TObject);
var
a,c:Cardinal;
n:Integer;
D:Double;
i:Integer;
b:string;
begin
c:=0;
for n:=0 to 99 do
begin
a:=GetTickCount;
for i:=0 to 999999 do
begin
b:=Copy(s,1,20);
end;
a:=GetTickCount-a;
C:=C+A;
end;
D:=C/100;
Label1.Caption:=FloatToStr(D);
end;
第二个用MoveMemory函数:
procedure TForm1.Button2Click(Sender: TObject);
var
a,c:Cardinal;
n:Integer;
d:Double;
i:Integer;
b:string;
begin
c:=0;
for n:=0 to 99 do
begin
a:=GetTickCount;
for i:=0 to 999999 do
begin
SetLength(b,20);
MoveMemory(@b[1],@s[1],20);
end;
a:=GetTickCount-a;
C:=C+A;
end;
D:=C/100;
Label2.Caption:=FloatToStr(D);
end;
其中:
procedure TForm1.FormCreate(Sender: TObject); begin s:='HuangJackyJackyHuang'; end;
看下实验数据:
1用了 264
2用了 169
当然这是在运行了9999900次才看出来的,如果我们把s赋值成一个很长的字符串看看
当字符串s有100个字符,Copy 100个:
1用了266
2用了181
继续 S有200个字符,Copy 200个的情况
1 244
2 186
可以看出来在短字符串的情况下MoveMemory肯定要快一些,后面字符串增长Copy效率没有下降,
但是SetLength + MoveMemory就下降了
最后s有400个字符Copy 400个,但是我把SetLength放在循环外面了,也就是只SetLength一次,也就只比较Copy和MoveMemory这两个了.
1 216
2 89
哈哈今天换在台式机了,所以2个运行时间都减少了很多.
再测试下SetLength在循环体里面的情况 用了147.
这下子我们可以看出来一个SetLength耗了多少资源.
OK,我们看见了这个结果,可是为什么会这样呢?有朋友想了解没有?
要了解就要深入函数内部去:
我们先看1的情况:
Unit1.pas.46: b:=Copy(s,1,400);
lea eax,[ebp-$14]
push eax
mov ecx,$00000190
mov edx,$00000001
mov eax,[esi+$00000308]
call @LStrCopy
//跟进去
-------------@LStrCopy---------
push ebx
test eax,eax
jz +$2d
mov ebx,[eax-$04]
test ebx,ebx
jz +$26
dec edx
jl +$1b
cmp edx,ebx
jnl +$1f
sub ebx,edx
test ecx,ecx
jl +$19
cmp ecx,ebx
jnle +$11
add edx,eax
mov eax,[esp+$08]
//这里会跳走我们继续跟踪
call @LStrFromPCharLen
jmp +$11
xor edx,edx
jmp -$1b
mov ecx,ebx
jmp -$15
mov eax,[esp+$08]
call @LStrClr
pop ebx
ret $0004
ret
---------@LStrFromPCharLen------------
push ebx
push esi
push edi
mov ebx,eax
mov esi,edx
mov edi,ecx
mov eax,edi
//这里还要跟
call @NewAnsiString
mov ecx,edi
mov edi,eax
test esi,esi
jz +$09
mov edx,eax
mov eax,esi
//这里
call Move
mov eax,ebx
//这里
call @LStrClr
mov [ebx],edi
pop edi
pop esi
pop ebx
ret
mov eax,eax
push ebp
mov ebp,esp
push $00
push $00
push edx
push eax
mov eax,[ebp+$08]
------------@NewAnsiString------------
test eax,eax
jle +$24
push eax
add eax,$0a
and eax,-$02
push eax
//继续,这里分内存了,快要到尽头了
call @GetMem
pop edx
mov word ptr [edx+eax-$02],$0000
add eax,$08
pop edx
mov [eax-$04],edx
mov [eax-$08],$00000001
ret
xor eax,eax
ret
------------@GetMem------------
push ebx
push ecx
mov ebx,eax
test ebx,ebx
jle +$1a
mov eax,ebx
//这里调用SysGetMem,我们不用跟了,因为SetLength肯定也会用到这个函数,抵消,哈哈
call dword ptr [MemoryManager]
mov [esp],eax
cmp dword ptr [esp],$00
jnz +$0e
mov al,$01
call Error
jmp +$05
xor eax,eax
mov [esp],eax
mov eax,[esp]
pop edx
pop ebx
ret
lea eax,[eax+$00]
-------------接下来是Move 但是MoveMemory也是调用这个函数,抵消,不看了----------
-----------@LStrClr这个要进去看,不看对不起观众--------------
mov edx,[eax]
test edx,edx
jz +$1c
mov [eax],$00000000
mov ecx,[edx-$08]
dec ecx
jl +$10
lock dec dword ptr [edx-$08]
jnz +$0a
push eax
lea eax,[edx-$08]
//这里又FreeMem,不看了.
call @FreeMem
pop eax
ret
nop
----------------整体过程--------------
Copy -> @LStrFromPCharLen -> @NewAnsiString -> @GetMem
-> Move
-> @LStrClr -> @FreeMem
好了这个流程走完.
基本上是3步,分空间,复制数据,收尾接下来看看2的情况:
Unit1.pas.71: SetLength(b,400); lea eax,[ebp-$14] mov edx,$00000190 //这个看一下 call @LStrSetLength Unit1.pas.72: MoveMemory(@b[1],@s[1],400); lea eax,[esi+$00000308] //这个干什么的 call @UniqueStringA push eax lea eax,[ebp-$14] call @UniqueStringA mov ecx,$00000190 pop edx //这里 call MoveMemory --------------@@LStrSetLength---------- push ebx push esi push edi mov ebx,eax mov esi,edx xor edi,edi test edx,edx jle +$48 mov eax,[ebx] test eax,eax jz +$23 cmp dword ptr [eax-$08],$01 jnz +$1d sub eax,$08 add edx,$09 push eax mov eax,esp call @ReallocMem pop eax add eax,$08 mov [ebx],eax mov [eax-$04],esi mov byte ptr [esi+eax],$00 //这里直接跳过去,然后就到MoveMemory这句了 jmp +$28 mov eax,edx //这个是不会被执行的. call @NewAnsiString mov edi,eax mov eax,[ebx] test eax,eax jz +$10 ------------@ReallocMem------------------ mov ecx,[eax] test ecx,ecx jz +$32 test edx,edx jz +$18 push eax mov eax,ecx call dword ptr [MemoryManager + $8] pop ecx or eax,eax jz +$19 mov [ecx],eax ret --------------@UniqueStringA---------- jmp InternalUniqueString ret mov eax,eax ----------InternalUniqueString---------- mov edx,[eax] test edx,edx jz +$38 mov ecx,[edx-$08] dec ecx jz +$32 mov eax,edx ret ---------MoveMemory--------------- xchg eax,edx call Move ret -------------------------整个过程------------------ SetLength -> @LStrSetLength -> @ReallocMem MoveMemory -> 2次@UniqueStringA -> Move ------------------------------------------------------------
对比2个过程
我们可以看到SetLength + MoveMemory 比 Copy 少了第三步.
从我们上面的实验结果我们也能看到 第2种方法比第1种方法 少用了1/3左右的时间.
感觉用PChar然后GetMem分配空间会跟快一些.这里就不测试了,有兴趣的朋友可以测试一下.
今天就到这里了,我是DH.
浙公网安备 33010602011771号