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.