• 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.

  • 相关阅读:
    hihocoder 1142 三分·三分求极值(三分)
    poj 3304 Segments(计算直线与线段之间的关系)
    poj 1269 Intersecting Lines(判断两直线关系,并求交点坐标)
    poj 2398 Toy Storage(计算几何 点线关系)
    poj 2318 TOYS(计算几何 点与线段的关系)
    计算几何基础(模板)
    Jmeter-基本组成
    java-面向对象
    性能测试基础
    java-多线程
  • 原文地址:https://www.cnblogs.com/huangjacky/p/1625073.html
Copyright © 2020-2023  润新知