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.

posted @   HuangJacky  阅读(3312)  评论(1编辑  收藏  举报
努力加载评论中...
AdminLogin
点击右上角即可分享
微信分享提示