String 如何将PChar的一部分提取到字符串中?

String 如何将PChar的一部分提取到字符串中?,string,delphi,optimization,pchar,String,Delphi,Optimization,Pchar,在分析过程中,我遇到了一个需要花费大量时间的函数,但基本上可以归结为以下非常简单的代码: function GetSubstring(AInput: PChar; AStart, ASubstringLength: Integer): string; begin Result := Copy(AInput, AStart, ASubstringLength); end; 此函数返回预期的子字符串,但对于较长的输入,它不能很好地伸缩。我在CPU视图中查看了汇编代码,据我所知(我通常不在汇编级

在分析过程中,我遇到了一个需要花费大量时间的函数,但基本上可以归结为以下非常简单的代码:

function GetSubstring(AInput: PChar; AStart, ASubstringLength: Integer): string;
begin
  Result := Copy(AInput, AStart, ASubstringLength);
end;
此函数返回预期的子字符串,但对于较长的输入,它不能很好地伸缩。我在CPU视图中查看了汇编代码,据我所知(我通常不在汇编级别工作),似乎
AInput
在调用
Copy
之前被隐式转换为字符串

但由于此时字符串/字符数组的长度未知,转换代码必须遍历
PChar
的长度,直到找到空终止符。这就解释了较长输入的可怕伸缩性

然而,由于调用者传入了
PChar
的长度,我最初认为可以将该方法转换为使用
SetString

function GetSubstring(AInput: PChar; AStart, ASubstringLength: Integer): string;
begin
  SetString(Result, AInput + AStart - 1, ASubstringLength);
end;
除了
SetString
以零为基础工作(而不是以复制为基础),似乎还有许多其他的小事情
Copy
在验证其输入方面所做的,并不是所有这些都记录在案(例如,任何小于1的起始值都更改为1)。因此,上述天真的实现并不总是像最初的实现那样工作

我的目标是尽可能多地复制
Copy
例程,因为此函数是库的一部分,已被我的同事广泛使用

我想知道下面的实现是否实现了这一点,或者我是否需要知道
Copy
的任何其他注意事项。注意:
FLength
是来自此函数所属模块中另一部分的
AInput
的实际长度。我删除了这个例子中的其他部分

function GetSubstring(AInput: PChar; AStart, ASubstringLength: Integer): string;
begin
  if (AInput = nil) then begin
    Result := '';
  end else begin
    if (AStart < 1) then begin
      AStart := 0;
    end else begin
      AStart := AStart - 1;
    end;
    if (ASubstringLength + AStart > FLength) then begin
      ASubstringLength := FLength - AStart;
    end;
    SetString(Result, AInput + AStart, ASubstringLength);
  end;
end;
函数GetSubstring(AInput:PChar;AStart,ASubstringLength:Integer):字符串;
开始
如果(AInput=nil),则开始
结果:='';
结束,否则开始
如果(AStart<1),则开始
AStart:=0;
结束,否则开始
AStart:=AStart-1;
结束;
如果(ASubstringLength+AStart>FLength),则开始
ASubstringLength:=FLength-AStart;
结束;
设置字符串(结果、输入+AStart、ASubstringLength);
结束;
结束;

我使用Delphi 2006,但我认为这与其他版本的产品(至少是非Unicode)没有什么不同。

让我们考虑角的情况。我认为它们是:

  • AInput
    无效
  • AStart<1
  • AStart>FLength
  • ASubstringLength<0
  • ASubstringLength+(AStart-1)>FLength
  • 在我看来,我们可以忽略案例1。调用者有责任提供有效的
    PChar
    。事实上,在我看来,您检查
    AInput nil
    已经太过分了,因为
    nil
    不是有效的
    PChar

    在剩下的部分中,你已经讲到了2和5,但没有讲到3和4。因此,如果用户提供的
    AStart
    值太大,那么您将读取字符串的末尾。同样,用户可以随时提供负
    ASubstringLength
    。我认为您不需要任何人编写代码来检查这些案例,因为您显然非常有能力

    现在,如果您真的关心每一点性能,那么您不应该检查这些情况。要求用户传递有效参数。在调试模式下,使用
    {$IFOPF D+}
    Assert
    可以检查输入。当然,如果这些参数来自外部来源,那么它们应该得到验证


    另一方面,原始代码受到的最大性能影响是不必要地扫描整个字符串,并复制到中间堆分配的字符串。一旦您删除了这些,那么进一步提高性能的机会就会大大减少。

    您应该尝试从地址
    AInput+(AStart*SizeOf(PChar))
    复制内存,而不是将
    PChar
    转换为
    stringlength*SizeOf(PChar))
    ,长度为
    ASubstringLength*SizeOf(PChar)
    @Result
    ,因为作为指针处理
    结果要容易得多


    Move
    过程可以做到这一点。

    您也可以尝试内联
    GetSubString()
    。“除了设置字符串以零为基础工作(而不是以一为基础作为副本)”-
    SetString()
    没有索引,句点。它在起始指针上操作,而不是索引
    Copy()
    有一个基于1的索引参数,因此原始的
    GetSubstring()
    也有一个基于1的
    AStart
    。您最初修改的
    GetSubstring()
    计算的指针错误。它需要使用
    -1
    而不是
    +1
    ,以保留与
    Copy()
    相同的语义(不包括角落案例):
    SetString(Result,Ainput+(AStart-1),ASubstringLength)@RemyLebeau你说得对。我过度简化了
    Copy
    SetString
    之间的差异。此外,由于我无法访问我的工作机器,我不得不从内存中复制代码,这导致+1而不是-1和
    SetString
    函数/过程混淆。现在修复了这些问题。@LURD很有趣,在内联该方法之后,它花费的时间是非内联版本的两倍。不确定这是为什么,也不知道这是否与我测量它的方式有关;微观基准测试充满了问题。但是,不扫描整个字符串并每次复制它已经足够提高性能了。实际上,代码使用的是
    System.SetString()
    函数:
    过程SetString(var s:string;buffer:PChar;len:Integer)为什么你认为不是呢?SetString确实是这样做的,这不是我的错。我把
    SetString
    UniqueString
    搞混了。我猜SetString检查空终止符,所以这是一个区别。但是我的