我正在使用Delphi 2009.在我的程序中,我一直在努力优化所有Delphi代码以提高速度和内存使用率,尤其是我的Unicode字符串处理。
我有以下声明:
Result := Result + GetFirstLastName(IndiID, 1);
当我调试该行时,从GetFirstLastName函数返回时,它会跟踪系统单元中的例程_UStrArrayClr:
procedure _UStrArrayClr(var StrArray; Count: Integer);
asm
JMP _LStrArrayClr
end;
这会调用_LStrArrayClr:
procedure _LStrArrayClr(var StrArray; cnt: longint);
{$IFDEF PUREPASCAL}
var
P: Pointer;
begin
P := @StrArray;
while cnt > 0 do
begin
_LStrClr(P^);
Dec(cnt);
Inc(Integer(P), sizeof(Pointer));
end;
end;
{$ELSE}
asm
{ -> EAX pointer to str }
{ EDX cnt }
PUSH EBX
PUSH ESI
MOV EBX,EAX
MOV ESI,EDX
@@loop:
MOV EDX,[EBX] { fetch str }
TEST EDX,EDX { if nil, nothing to do }
JE @@doneEntry
MOV dword ptr [EBX],0 { clear str }
MOV ECX,[EDX-skew].StrRec.refCnt { fetch refCnt }
DEC ECX { if < 0: literal str }
JL @@doneEntry
LOCK DEC [EDX-skew].StrRec.refCnt { threadsafe dec refCount }
JNE @@doneEntry
LEA EAX,[EDX-skew].StrRec.codePage { if refCnt now zero, deallocate}
CALL _FreeMem
@@doneEntry:
ADD EBX,4
DEC ESI
JNE @@loop
POP ESI
POP EBX
end;
{$ENDIF}
并为每个字符运行一次循环,并从那里退出时调用_UStrCat:
procedure _UStrCat(var Dest: UnicodeString; const Source: UnicodeString);
asm
{ -> EAX pointer to dest }
{ EDX source }
TEST EDX,EDX // Source empty, nop.
JE @@exit
MOV ECX,[EAX] // ECX := Dest
TEST ECX,ECX // Nil source => assignment
JE _UStrAsg
PUSH EBX
PUSH ESI
PUSH EDI
MOV EBX,EAX // EBX := @Dest
MOV ESI,EDX // ESI := Source
CMP ESI,ECX
JE @@appendSelf
CMP [ECX-skew].StrRec.elemSize,2
JE @@destIsUnicode
CALL _EnsureUnicodeString
MOV EDI,EAX
MOV ECX,EAX
@@destIsUnicode:
PUSH 0
CMP [ESI-skew].StrRec.elemSize,2
JE @@sourceIsUnicode
MOV EDI,ECX
MOV EAX,ESI
MOV [ESP],ESI
CALL _UStrAddRef
MOV EAX,ESP
CALL _EnsureUnicodeString
MOV ESI,[ESP]
MOV ECX,EDI
@@sourceIsUnicode:
MOV EDI,[ECX-skew].StrRec.length // EDI := Length(Dest)
MOV EDX,[ESI-skew].StrRec.length // EDX := Length(Source)
ADD EDX,EDI // EDX := (Length(Source) + Length(Dest)) * 2
TEST EDX,$C0000000
JNZ @@lengthOverflow
MOV EAX,EBX
CALL _UStrSetLength // Set length of Dest
MOV EAX,ESI // EAX := Source
MOV ECX,[ESI-skew].StrRec.length // ECX := Length(Source)
@@noTemp:
MOV EDX,[EBX] // EDX := Dest
SHL EDI,1 // EDI to bytes (Length(Dest) * 2)
ADD EDX,EDI // Offset EDX for destination of move
SHL ECX,1 // convert Length(Source) to bytes
CALL Move // Move(Source, Dest + Length(Dest)*2, Length(Source)*2)
MOV EAX,ESP // Need to clear out the temp we may have created above
MOV EDX,[EAX]
TEST EDX,EDX
JE @@tempEmpty
CALL _LStrClr
@@tempEmpty:
POP EAX
POP EDI
POP ESI
POP EBX
RET
@@appendSelf:
CMP [ECX-skew].StrRec.elemSize,2
JE @@selfIsUnicode
MOV EAX,EBX
XOR EDX,EDX
CALL _EnsureUnicodeString
MOV ECX,EAX
MOV EAX,EBX
@@selfIsUnicode:
MOV EDI,[ECX-skew].StrRec.length
MOV EDX,EDI
SHL EDX,1
TEST EDX,$C0000000
JNZ @@lengthOverflow
CALL _UStrSetLength
MOV EAX,[EBX]
MOV ECX,EDI
PUSH 0
JMP @@noTemp
@@lengthOverflow:
JMP _IntOver
@@exit:
end;
并贯穿整个例程。
我的“结果”是一个字符串,因此是Unicode。我的GetFirstLastName返回一个Unicode字符串。不需要转换字符集。
我无法确定这些系统程序正在做什么,但它们给我的例程增加了很多开销。
他们在做什么?他们有必要吗?如果没有必要,我怎样才能阻止编译器调用这些例程?
答案 0 :(得分:8)
LStrArrayClear没有为每个字符运行一次循环;它在数组中每个字符串运行一次,减少引用计数并释放字符串,如果它达到0.这将由编译器插入以清除分配为局部变量的任何字符串,或者它创建的任何临时字符串以保存结果连接两个字符串。
UStrCat是字符串连接例程。这是string1 + string2
翻译的内幕。编译器确定它应该产生一个Unicode字符串,所以它需要两个输入字符串,测试它们两个以查看它们本身是否为Unicode,如果它们不是,则转换它们(但是你的是,所以转换得到跳过,然后设置结果的大小并复制数据。
UStrCat是必要的,你无能为力。 LStrArrayClear是事情变得有点模糊的地方。当您创建一个使用字符串的例程时,编译器必须分配足够的临时字符串来处理您可以在那里执行的所有操作,无论您是否执行此操作。然后它必须在之后清除它们。因此,通过将不常见的任务移动到其他函数来减少不必要的字符串操作可能会有所帮助,尤其是在紧密循环中。
例如,你经常看到这样的事情吗?
if SomethingIsVeryWrong then
raise ETimeToPanic.Create('Everybody panic! File ' + filename + ' is corrupt at address ' + intToStr(FailureAddress) + '!!!');
此错误消息包含5个不同的子字符串。即使它设法通过重用它们来优化事物,它仍然需要分配至少两个临时字符串来使其工作。假设这是在一个紧密的循环内发生的,你不要指望这个错误经常发生,如果有的话。您可以通过将字符串连接卸载到Format调用中来消除临时字符串。事实上,这是一种非常方便的优化,它内置于Exception
。
if SomethingIsVeryWrong then
raise ETimeToPanic.CreateFmt('Everybody panic! File %s is corrupt at address %d!!!', [filename, FailureAddress]);
是的,对Format的调用运行速度明显慢于直接连接,但是如果出现问题,它只会运行一次并且性能是您最不担心的。
答案 1 :(得分:6)
编译器通常会创建临时值来保存表达式的中间值。这些临时工需要“最终确定”或清理完毕。由于编译器不知道某个temp是否实际被使用过(如果它看到变量仍为nil,它将跳过最终化),它将始终尝试清理传递。
答案 2 :(得分:2)
您可能也对以下内容感兴趣:
答案 3 :(得分:2)