将 Olevariant 二维双精度数组转换为动态二维双精度数组的最快方法

Fastest way to Convert Olevariant 2D Array of Double to Dynamic 2D Double Array

我有一个二维 OleVariant 双精度矩阵 xyInput := VarArrayCreate([0, Count-1, 0, 1], varDouble );

我想要使用 move().

转换(尽可能快)到普通二维动态数组 DestArray : array[0..1] of array of Double

在解决这个问题的过程中,我使用 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 个字节是什么?

这是一个完整的代码片段:

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;

But I have discovered that the address diff between Pointer(DestArray[0]) and Pointer(DestArray[1]) is 56 bytes.

这是非常值得期待的。好吧,更清楚地说,根本没有理由期望 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 中分配一个线性数组,并将指向该数组的指针放入您的变体中,从而完全避免复制。

是合理的。