如何加快转换功能?

Jer*_*dge 2 delphi performance loops

我有这个函数调用IntToStrLen,它将一个整数转换为填充大小的字符串.例如,42大小为的整数值4将导致字符串__42(使用给定字符填充,默认为空格).

问题是当这个函数被批量使用时(例如循环中的1,000,000次),它会给循环增加额外的重量.我正在使用它的循环,没有这个功能,大约需要20秒,但是使用这个功能,我现在还在等待大约5分钟后完成此功能.

如何加快以下功能?

function IntToStrLen(const Value: Integer; const Len: Integer;
  const Fill: String = ' '): String;
var
  T: String;
begin
  Result:= IntToStr(Value);                      //convert result
  if Length(Result) > Len then
    Result:= Copy(Result, 1, Len)                //forcefully truncate
  else if Length(Result) < Len then begin
    T:= '';
    while Length(T) < (Len - Length(Result)) do  //fill space with character
      T:= T + Fill;
    Result:= T + Result;                         //return combination
  end;
end;
Run Code Online (Sandbox Code Playgroud)

Dav*_*nan 6

您可以在此处进行的绝对第一项更改是避免堆分配.您的代码目前有多个堆分配.您的目标是编写零堆分配的函数.

这意味着您需要调用者来分配和提供缓冲区.通常他们会使用堆栈分配的缓冲区来执行此操作,因此无需分配(或解除分配)任何成本.如果调用者确实需要a string,它总是在堆上分配,那么调用者可以通过调用来包装你的函数SetString.

函数原型可能如下所示:

procedure IntToStrLen(const Value, Len: Integer; var Buffer: array of Char;
  const Fill: Char = ' ');
Run Code Online (Sandbox Code Playgroud)

这里要强调的第一点是Fill必须是a Char.您的使用string效率低下,并允许调用者调用长度不等于1的填充"字符".这样做当然会破坏你的功能,因为它会返回一个长度不等于的值Len.

请注意,实现不得调用,IntToStr因为这涉及堆分配.因此,您需要将自己的堆分配空闲整数写入十进制文本转换代码,因为令人惊讶的是,RTL不提供此类功能.当我这样做时,我使用这样的代码:

procedure DivMod(Dividend, Divisor: Cardinal; 
  out Quotient, Remainder: Cardinal);
{$IFDEF CPUX86}
asm
        PUSH  EBX
        MOV   EBX,EDX
        XOR   EDX,EDX
        DIV   EBX
        MOV   [ECX],EAX
        MOV   EBX,Remainder
        MOV   [EBX],EDX
        POP   EBX
end;
{$ELSE IF Defined(CPUX64)}
asm
        .NOFRAME
        MOV   EAX,ECX
        MOV   ECX,EDX
        XOR   EDX,EDX
        DIV   ECX
        MOV   [R8],EAX
        MOV   [R9],EDX
end;
{$ELSE}
  {$Message Error 'Unrecognised platform.'}
{$ENDIF}

function CopyIntegerToCharBuffer(const Value: Integer; 
  var Buffer: array of Char): Integer;
var
  i, j: Integer;
  val, remainder: Cardinal;
  negative: Boolean;
  tmp: array [0..15] of Char;
begin
  negative := Value<0;
  val := abs(Value);
  Result := 0;
  repeat
    DivMod(val, 10, val, remainder);
    tmp[Result] := Chr(remainder + ord('0'));
    inc(Result);
  until val=0;
  if negative then begin
    tmp[Result] := '-';
    inc(Result);
  end;
  Assert(Result<=Length(Buffer));

  i := 0;
  j := Result-1;
  while i<Result do begin
    Buffer[i] := tmp[j];
    inc(i);
    dec(j);
  end;
end;
Run Code Online (Sandbox Code Playgroud)

现在,您可以在不触及堆的情况下对整数进行十进制文本表示.从那里开始,这是一个简短的功能.

procedure IntToStrLen(const Value, Len: Integer; var Buffer: array of Char;
  const Fill: Char = ' ');
var
  tmp: array [0..15] of Char;
  i, N: Integer;
begin
  Assert(Length(Buffer)>=Len);
  N := CopyIntegerToCharBuffer(Value, tmp);
  if N>=Len then begin
    Move(tmp, Buffer, SizeOf(Char)*Len);
  end else begin
    for i := 0 to Len-N-1 do begin
      Buffer[i] := Fill;
    end;
    Move(tmp, Buffer[Len-N], SizeOf(Char)*N);
  end;
end;
Run Code Online (Sandbox Code Playgroud)

此时,您将获得大部分可用的性能优势.从现在开始,你将会收益递减.你可以微优化CopyIntegerToCharBuffer是在做SysUtils._IntToStr32了实例.除此之外,我确信IntToStrLen可以通过明智地使用汇编程序来优化实现.但是这样的优化不会产生你迄今为止从避免堆中获得的好处.

当然,所有这些都假设您已正确识别性能瓶颈.通过静态分析代码,您可以轻松地假设您知道性能瓶颈在哪里.除非你真的对它进行了详细的描述,否则你会发现你的直觉对于在哪里投入优化工作是一个糟糕的判断.