在紧密循环中复制数据,其中函数" Move"不能使用

时间:2017-01-10 05:59:54

标签: assembly x86 freepascal

在我的一个软件项目中,我需要"紧密循环的最佳速度,将数组中的索引数据复制到同一数组的不同部分。

请考虑这个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,但那是一辈子的事。

2 个答案:

答案 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例程。