在我的一个软件项目中,我需要"紧密循环的最佳速度,将数组中的索引数据复制到同一数组的不同部分。
请考虑这个freepascal代码:
TYPE
tIndex : ARRAY OF BYTE;
tValues: ARRAY OF CARDINAL;
VAR
Index : tIndex;
Values: tValues;
CONST
AnyCardinalSize = 65535;
PROCEDURE InitializeArrays;
BEGIN
SetLength(Index, AnyCardinalSize);
SetLength(Values,AnyCardinalSize);
END;
PROCEDURE MoveData(CONST StartPosition,EndPosition,TargetPosition : CARDINAL);
VAR {Note: TargetPosition is ALWAYS larger than EndPosition.}
x, InsertOffset : CARDINAL;
BEGIN
InsertOffset := TargetPosition-StartPosition;
FOR x := StartPosition TO EndPosition DO BEGIN
Values[x+InsertOffset] := Values[Index[x]];
END;
END;
我的例程MoveData
按预期工作,但我想研究用汇编代码替换它的可能性,以便尽可能获得一点速度。
如果有人举例说明如何用汇编语言完成,那么我将非常感激。我在Commodore 64上做了一些ASM,但那是一辈子的事。
答案 0 :(得分:3)
大多数优化来自hoisting表达式中不变的代码部分,然后执行强度降低以减少变体的数量。学习在进入汇编程序之前优化Pascal,即使只是因为反汇编的Pascal为进一步的汇编程序优化提供了一个框架,并且在测试汇编程序代码时可以作为比较。
为了更好地识别常量部分,我们转换为基于0的for循环,其中n = 0..endposition-startposition,表达式变为:
FOR n := 0 TO EndPosition-StartPosition DO
BEGIN
Values[n+InsertOffset+StartPosition ] := Values[Index[n+StartPosition]];
END;
然后我们从循环不变的部分中分解出常量部分,并引入指针语法。 T是values数组的类型,PT是指向它的指针。类似地,TI是索引数组的类型,PTI是指向它的指针。
var StartValue : PT;
StartIndex : PTI;
StartValue:=@Values[InsertOffset+StartPosition]
StartIndex:=@Index[StartPosition];
FOR n := 0 TO EndPosition-StartPosition DO
Startvalue[n]:=Values[StartIndex[n]]; // slightly depended on FPC dialect mode
现在,startvalue [n]计算为addressof(startvalue [0])+ n * sizeof(T);但是,如果我们记住前一个循环中的地址值(在Startvalue中),则差值变为PT:= PT + sizeof(T);
我们将乘法转换为add,但更重要的是,我们从表达式中删除了一个变量(而不是startvalue和x(或n),我们现在只记得startvalue)。这称为strength reduction,我们也可以将其应用于索引数组:
FOR n := 0 TO EndPosition-StartPosition DO
begin
Startvalue^:=Values[StartIndex^];
inc(StartValue); // inc increments with element size and we declared as PT, so this means inc(startvalue,sizeof(T));
inc(StartIndex);
end;
不幸的是,这并不能完全消除对values数组的需求,但是这个循环在循环中使用了三个值(startvalue,startindex和values []),而原始的有很多(index,values,x,startposition) ,insertoffset)。此外,两者都有一些东西可以识别迭代的结束。
在我们的情况下,可能是当startvalue到达数组的末尾时,但为此我们必须使用while:
var StartValue,EndValue : PT;
StartIndex : PTI;
StartValue:=@Values[InsertOffset+StartPosition]
EndValue:=@Values[InsertOffset+EndPosition]
StartIndex:=@Index[StartPosition];
while (StartValue<=Endvalue) do
begin
Startvalue^:=Values[StartIndex^];
inc(StartValue); // inc increments with element size and we declared as PT, so this means inc(pbyte(startvalue),sizeof(T));
inc(StartIndex);
end;
理论上,编译器可以进行这些优化。 C编译器通常在正确指导时执行,但大多数Pascal编译器尚未达到该级别。但是他们没有理由不这样做。
启动汇编程序的最佳方法是首先优化Pascal并尝试理解生成的代码。
我多年来一直在Delphi中进行图像分析,而汇编程序只是手动纠正编译器没有得到它(例如没有提升或选择次优的不变量),或者当寄存器分配变得很糟糕时。
唯一的另一种情况是SSE中的整个图像操作,但这不适用于此。
答案 1 :(得分:1)
首先,在继续之前,我认为您必须确定以这种方式复制数据确实是瓶颈,而且没有其他可用的策略可以完全消除复制的需要。
如果您确定需要更快的复制程序,值得注意的是,这些优化可能非常棘手并依赖于硬件以及内存布局/对齐/数据组织和规模。如果没有相当多的经验或时间,您很可能不会想出一个值得付出努力的卓越解决方案。
但是,优化的内存复制/移动例程 可用。特别是,你可能想看看Agner Fog的asmlib,其中包含可以从编译代码中调用的优化的mem copy / move例程。