我可以消除额外的Unicode字符串调用(Delphi)

lke*_*ler 2 delphi string unicode optimization

我正在使用Delphi 2009.在我的程序中,我一直在努力优化所有Delphi代码以提高速度和内存使用率,尤其是我的Unicode字符串处理.

我有以下声明:

    Result := Result + GetFirstLastName(IndiID, 1);
Run Code Online (Sandbox Code Playgroud)

当我调试该行时,从GetFirstLastName函数返回时,它会跟踪系统单元中的例程_UStrArrayClr:

procedure _UStrArrayClr(var StrArray; Count: Integer);
asm
        JMP     _LStrArrayClr
end;
Run Code Online (Sandbox Code Playgroud)

这会调用_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}
Run Code Online (Sandbox Code Playgroud)

并为每个字符运行一次循环,并从那里退出时调用_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;
Run Code Online (Sandbox Code Playgroud)

并贯穿整个例程.

我的"结果"是一个字符串,因此是Unicode.我的GetFirstLastName返回一个Unicode字符串.不需要转换字符集.

我无法确定这些系统程序正在做什么,但它们给我的例程增加了很多开销.

他们在做什么?他们有必要吗?如果没有必要,我怎样才能阻止编译器调用这些例程?

Mas*_*ler 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) + '!!!');
Run Code Online (Sandbox Code Playgroud)

此错误消息包含5个不同的子字符串.即使它设法通过重用它们来优化事物,它仍然需要分配至少两个临时字符串来使其工作.假设这是在一个紧密的循环内发生的,你不要指望这个错误经常发生,如果有的话.您可以通过将字符串连接卸载到Format调用中来消除临时字符串.事实上,这是一个非常方便的优化,它是内置的Exception.

if SomethingIsVeryWrong then
   raise ETimeToPanic.CreateFmt('Everybody panic! File %s is corrupt at address %d!!!', [filename, FailureAddress]);
Run Code Online (Sandbox Code Playgroud)

是的,对Format的调用将比直接连接慢得多,但是如果出现问题,它只会运行一次并且性能是您最不担心的.

  • @lkessler:要记住的另一件事是这些例程是ASM优化的,它们非常快.*尝试通过Sampling Profiler运行代码,看看你是否花费了大量的时间.优化是很好的,但如果你正在处理错误的问题,那就浪费时间了...... (2认同)
  • 很好的答案,梅森! (2认同)

All*_*uer 6

编译器通常会创建临时值来保存表达式的中间值.这些临时工需要"最终确定"或清理完毕.由于编译器不知道某个temp是否实际被使用过(如果它看到变量仍为nil,它将跳过最终化),它将始终尝试清理传递.