Delphi - Copy函数效率的问题
技术交流,DH讲解.
最近和肥鸟交流了下关于字符串方面的知识,而这篇文章是很久以前写的,现在发出来吧.
我们写两段代码来对比下:
第一个用Copy函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 ; |
其中:
1 2 3 4 | 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的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | 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的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | 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.
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步