将Olevariant 2D数组转换为动态2D双阵列的最快方法

时间:2015-11-26 15:42:58

标签: arrays delphi pointers dynamic-arrays variant

我有一个双倍xyInput := VarArrayCreate([0, Count-1, 0, 1], varDouble );的2D OleVariant矩阵。

我希望使用DestArray : array[0..1] of array of Double转换(尽可能快)到普通的2D动态数组move()

在解决此问题的过程中,我使用Count=5为每个维度提供了预期的40个字节。但我发现Pointer(DestArray[0])Pointer(DestArray[1])之间的地址差异为56个字节。

那么中间的16个字节是什么?我不知道前8个字节,但最后8个字节是关于数组维度的信息。

因此,move()无法一步完成Move(VarArrayData^, Pointer(DestArray[0])^, BytesToMove );

我找到了一种方法,通过使用2个单独的动作,但我仍然觉得它可以做得更优雅。

问题

  1. 实际上是否有更简单快捷的方式?
  2. 是否有任何编译器指令或类似物可以更改动态数组的内存布局以允许move()在一个步骤中工作?
  3. 出于好奇,Pointer(DestArray[0])Pointer(DestArray[1])之间的内存空白中的前8个字节是什么?
  4. 以下是完整的代码段:

    procedure TForm1.FormCreate(Sender: TObject);
    
    procedure PrintEqualityVerdictLine( value1 : Double; value2 : Double );
    const
      cEqualVerdict : array[Boolean] of String = ( '!!!Not Equal!!!', 'Equal' );
    begin
      Memo1.Lines.Add(FloatToStr(value1) + ' =? ' + FloatToStr(value2) + '      ' + cEqualVerdict[ SameValue( value1, value2, 0.001 ) ] );
    end;
    
    procedure VariantArrayOfDoubleToDynamicDoubleArray;
    var
      xyInput : OleVariant;
      Count: Integer;
      n: Integer;
    
      DestArray : packed array[0..1] of packed array of Double;
    
      V_Ptr: PVarData;
      VarArrayData: PVarData;
      BytesToMove: Integer;
    
      SourceBytePtr : PByte;
      BytesToMovePerColumn: Integer;
      DestBytePtr: PByte;
    begin
      // create 2 column OleVariant array:
      Count := 5;
      xyInput := VarArrayCreate([0, Count-1, 0, 1], varDouble );
    
      // fill test data:
      for n := 0 to Count-1 do
      begin
        xyInput[n, 0] := 1.0 * n;
        xyInput[n, 1] := 2.0 * Count + n;
      end;
    
      SetLength(DestArray[0], Count);
      SetLength(DestArray[1], Count);
    
      V_Ptr := PVarData(@xyInput);
      if ((V_Ptr^.VType and $F000 ) = varArray) and
         ((V_Ptr^.VType and varTypeMask ) = varDouble)
      then
      begin
        VarArrayData := PVarData(V_Ptr^.VArray^.Data);
        BytesToMovePerColumn := Count * V_Ptr^.VArray^.ElementSize;
        BytesToMove := BytesToMovePerColumn*V_Ptr^.VArray^.DimCount;
    
        // print 16 discovered intermediate bytes of the DestArray:
        DestBytePtr := Pointer(DestArray[0]);
        Inc(DestBytePtr, BytesToMovePerColumn);
        for n := 1 to 16 do
        begin
          Memo1.Lines.Add('byte['+IntToStr(n) + ']: ' + IntToStr( DestBytePtr^ ) );
          Inc(DestBytePtr);
        end;
    
    //    This does NOT work: col 1 of arr gets offset due to 16 discovered intermediate bytes:
    //        Move(VarArrayData^, Pointer(DestArray[0])^, BytesToMove );
    
    //    This works:
        SourceBytePtr := PByte(VarArrayData);
        Move(SourceBytePtr^, Pointer(DestArray[0])^, BytesToMovePerColumn );
        Inc(SourceBytePtr, BytesToMovePerColumn);
        Move(SourceBytePtr^, Pointer(DestArray[1])^, BytesToMovePerColumn );
      end;
    
      // print:
      Memo1.Lines.Add('VariantArrayOfDoubleToDoubleArray:');
    
      Memo1.Lines.Add('col 0:');
      for n := 0 to Count - 1 do
        PrintEqualityVerdictLine( xyInput[n, 0], DestArray[0, n] );
      Memo1.Lines.Add('');
    
      Memo1.Lines.Add('col 1:');
      for n := 0 to Count - 1 do
        PrintEqualityVerdictLine( xyInput[n, 1], DestArray[1, n] );
    end;
    
    begin
      Memo1.Lines.Clear;
      VariantArrayOfDoubleToDynamicDoubleArray;
    end;
    

1 个答案:

答案 0 :(得分:4)

  

但我发现Pointer(DestArray[0])Pointer(DestArray[1])之间的地址差异为56个字节。

这是非常值得期待的。好吧,为了更清楚,没有理由期望DestArray[0]DestArray[1]指向相邻的内存块。

您的类型是

array[0..1] of array of Double;

请注意,我删除了应用于数组时忽略的packed关键字。你在这里有一个包含两个指针的数组。这两个指针是独立的。看看如何分配动态数组。

SetLength(DestArray[0], Count);
SetLength(DestArray[1], Count);

每次调用SetLength都会导致单独的堆分配。毫无理由让内存相邻。在此之前,动态数组在数组的有效负载之前存储了额外的元数据块,并且每个内存块都有自己的内存管理器使用的元数据。因此,即使内存管理器偶然发生了相邻的内存块,元数据也会位于两个数组之间。顺便提一下,这个内存管理器元数据是你问题3的答案。

在技术方面,DestArray中的内容为jagged array。另一方面,您似乎正在寻找一个多维数组。 Delphi实际上并不支持动态多维数组。你所拥有的只是锯齿状阵列。如果你想要一个连续的内存块,那么你需要分配一个内存块并自己执行索引计算。

因此,就目前而言,如果继续使用锯齿状数组,则需要为每个内部数组执行一个副本。如果切换到线性阵列,那么您可以使用单个副本,但您必须执行自己的索引。当然,索引很容易做到,而且效率可能很高。最后,您可以提前在Delphi中分配线性数组,并将指向该数组的指针放入您的变体中,从而完全避免复制。