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

标签 delphi string optimization unicode

我使用的是 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。不需要转换字符集。

我无法真正说明这些系统过程在做什么,但它们给我的例行程序增加了很多开销。

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

最佳答案

LStrArrayClear 不会对每个字符运行一次循环;它为数组中的每个字符串运行一次,以减少引用计数并在它命中 0 时释放该字符串。这是由编译器插入的,用于清理分配为局部变量的任何字符串,或它创建的用于保存结果的任何临时字符串连接两个字符串。

UStrCat 是字符串连接例程。这就是 string1 + string2 在幕后的翻译。编译器确定它应该产生一个 Unicode 字符串,所以它接受两个输入字符串,测试它们本身是否是 Unicode,如果不是则转换它们(但你的是,所以转换得到skipped,) 然后设置结果的大小并复制数据。

UStrCat 是必需的,对此您无能为力。 LStrArrayClear 是事情变得有点模糊的地方。当您创建一个使用字符串的例程时,编译器必须分配足够的临时字符串来处理您可以在其中执行的所有操作,无论您是否做过。然后它必须在之后清除它们。因此,通过将不常见的任务移至其他函数来减少不必要的字符串操作会有所帮助,尤其是在紧密循环中。

例如,您多久看到一次这样的事情?

if SomethingIsVeryWrong then
   raise ETimeToPanic.Create('Everybody panic! File ' + filename + ' is corrupt at address ' + intToStr(FailureAddress) + '!!!');

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

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

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

关于delphi - 我可以消除额外的 Unicode 字符串调用吗(Delphi),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3137107/

相关文章:

delphi - IDE中的delphi软件包自动化安装程序

delphi - 如何在没有冗余列表的情况下迭代父/子层次结构中的对象?

javascript - cssText 还是单独的样式名称?

android - 在 Android SQLite 中使用日期的首选方式是什么?为什么?

sql - 在 MySQL 中使用集合的更快方法

Delphi:为什么断点有时不可用(IDE 上的绿色突出显示线)?

delphi - 为什么MessageDlg在Windows下只显示 "OK"按钮?

python - 这个编码是什么以及如何转换它?

c++ - memset 导致 std::string 赋值崩溃

C 从字符串的一部分获取数值