Performance 如何加速此转换功能?
我有一个名为Performance 如何加速此转换功能?,performance,delphi,loops,Performance,Delphi,Loops,我有一个名为IntToStrLen的函数,它将整数转换为填充大小的字符串。例如,大小为4的整数值42将产生一个字符串\uu 42(默认情况下用给定字符填充) 问题是,当批量使用此函数时(例如,在循环中使用1000000次),它会给循环增加额外的重量。我正在使用的循环,没有这个函数,大约需要20秒,但是有了这个函数,我现在仍然在等待这个函数完成,大约5分钟后 如何加速以下功能 function IntToStrLen(const Value: Integer; const Len: Integer
IntToStrLen
的函数,它将整数转换为填充大小的字符串。例如,大小为4
的整数值42
将产生一个字符串\uu 42
(默认情况下用给定字符填充)
问题是,当批量使用此函数时(例如,在循环中使用1000000次),它会给循环增加额外的重量。我正在使用的循环,没有这个函数,大约需要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;
函数IntToStrLen(常量值:整数;常量长度:整数;
常量填充:字符串=“”):字符串;
变量
T:弦;
开始
结果:=IntToStr(值)//转换结果
如果长度(结果)>Len,则
结果:=复制(结果,1,Len)//强制截断
否则,如果长度(结果)
试试这个变体(并且不处理负数)
函数IntToStrLen(常量值:整数;常量长度:整数;
常量填充:Char='':字符串;
变量
T:弦;
开始
结果:=IntToStr(值);
如果长度(结果)>Len,则
SetLength(Result,Len)//强制截断
否则,如果长度(结果)
p.S.如此奇怪的截断(2014=>20)-是您真正想要的吗?这里可以做的第一个绝对改变是避免堆分配。您的代码目前有多个堆分配。这里的目标是编写一个堆分配为零的函数 这意味着您需要调用方分配并提供缓冲区。通常,它们会使用堆栈分配的缓冲区来完成,因此分配(或取消分配)不会产生任何成本。如果调用者确实需要一个总是在堆上分配的
字符串
,那么调用者可以通过调用SetString
来包装函数
函数原型可能如下所示:
procedure IntToStrLen(const Value, Len: Integer; var Buffer: array of Char;
const Fill: Char = ' ');
这里要强调的第一点是Fill
必须是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;
此时,您将获得大部分可用的性能优势。从现在起,你将进入收益递减的状态。您可以像在SysUtils中那样对CopyIntegerToCharBuffer
进行微优化。除此之外,我相信明智地使用汇编程序可以优化IntToStrLen
的实现。但这样的优化不会带来任何好处,就像您迄今为止从避免堆中获得的好处一样
当然,所有这些都假设您已经正确识别了性能瓶颈。通过静态分析代码,很容易假设您知道性能瓶颈在哪里。除非您确实仔细地分析了它,否则您可能会发现您的直觉对在何处投资优化工作的判断很差。仍然需要一段时间,但我需要创建一个测试场来记录速度-因为我当前的测试正在加载/读取一个大的二进制文件。它将一个20mb文件中的每个字节转换成一个字符串并显示在字节网格中。显示一个大表可能需要很多时间。谁想看2000万个数字呢?就其实现的性质而言,结果必须是精确指定的长度,截断它当然不优雅,但这是确保它是完美长度的唯一方法。据你所知,我可能正在设计矩阵:-)我已经测试过了。在旧Athlon处理器上,对于Len=10和2000万个数字,我们的函数可以工作5-20秒(只有运算符SetLength(Result)的空函数可以工作2秒),所以在您的程序FWIW中还有另一个计时程序,您的代码在读取之前不会初始化T
。@DavidHeffernan字符串由您初始化know@JerryDodge我在所有场景中都会初始化托管类型的局部变量,这就是我们在这里讨论的,对吗?@StefanGlienkefunction GetText:String;开始结果:=结果+文本;结束对于X:=1到4的do begin ShowMessage(GetText),代码>然后;结束代码>应说明为何应初始化函数结果。函数的结果永远不会被重置为空白,因此它会不断添加到同一个字符串中。@Jerrydoge我很清楚这个问题-我建议您投票支持天文改进,非常感谢:-)在这里使用短字符串作为函数的返回值不是更容易吗?还是我错过了一些会让事情变慢的东西+1用于考虑非英特尔处理器。@Johan一个短字符串可能会有用。但是接下来需要将8位文本编码转换为16位文本编码。我没有考虑非英特尔处理器。您需要为ARM编写一个新的DivMod
@DavidHeffernan是否有一些避免堆分配的一般指导原则?@user3060326我要说的第一件事是,如果有明显的好处,只能尝试我答案中的代码。它确实使代码不可用
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;
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;