如何有效地将 OleVariant 数组复制到我自己的结构中?

How to copy an OleVariant array efficiently to my own structure?

我正在尝试将 OleVariant 数组复制到我自己的结构中。我收到来自外部 COM 调用的 OleVariant

大小为1000 x 500个元素(不知道是不是和这个结构定义一样:array of array of OleVariant)。

目前,我正在努力实现类似的目标:

result := Copy(Source, Amount)

但是 OleVariant 结构挡住了我的路。

如果我使用“经典”循环,它可以工作,但速度很慢(非常慢)。

aResult 当前定义为 TData = array of array of string;

procedure CopyResult(aResultCount: Integer; var aResult: TData; aSource: Variant);
var
  i, j: Integer;
  bVariantConversion: boolean;
begin    
  SetLength(aResult, aResultCount, VarArrayHighBound(aSource[0], 1));
  bVariantConversion := NullStrictConvert; // settings to manage how string conversion for Variant is handled.
  NullStrictConvert := False;
  try
    for i := VarArrayLowBound(aSource, 1) to VarArrayHighBound(aSource, 1) do
    begin
      for j := VarArrayLowBound(aSource[i], 1) to pred(VarArrayHighBound(aSource[i], 1)) do
      begin
        //nearly every execution pause is somewhere in this String Conversion or Array Function.
        aResult[i][j] := aSource[i][j]; //implicit conversion to string ... 
      end;
    end;
  finally
    NullStrictConvert := bVariantConversion;
  end;
end;

正如@Remy Lebau 提到的,检查 Vararray[x][y] 访问的边界是我的资源消耗时间的例程。我试图通过直接转到 OleVariantArray 元素来消除这种访问。

后果... 试图确定我的结构我想我找到了根。

  tmyVarType := VarType(aSource); //8204 => Array(VT_ARRAY = 0x2000 = 8192) + variant(VT_VARIANT = 0x000C = 12) 
  tmyVarType := VarType(aSource[0]); //8204
  tmyVarType := VarType(aSource[0][0]); //3 VT_I4 = 0x0003 = 3 is integer and this is correctly changin for the fields. 

所以我尝试在没有内置函数的情况下访问源代码以避免边界检查。

此代码中最大的瓶颈是 [] 运算符对每个 Variant 数组执行的边界检查,也可能对您的 aResult 数组执行边界检查。由于您已经在每个循环中处理边界,因此也无需验证循环内部的边界。

因此,如果性能对您来说是个问题,那么您可以使用 VarArrayLock() 访问每个数组中的底层 Variant 元素,使用指针算法在它们之间移动,消除那些冗余边界检查。

您还应该在外部数组的每次迭代中减少对 VarArray(Low|High)Bound(aSource[i], 1) 的冗余调用,因为您声称内部数组都具有相同的长度。所以你可以在进入循环之前预先计算。

尝试这样的事情:

type
  TStrArr = array of string;
  PStrArr = ^TStrArr;
  TData = array of TStrArr;

procedure CopyResult(aResultCount: Integer; var aResult: TData; aSource: Variant);
var
  i, j,
  OuterLBound, OuterHBound, OuterCount,
  InnerLBound, InnerHBound, InnerCount: Integer;
  pOuterVarArr, pInnerVarArr: PVariant;
  pOuterDynArr: PStrArr;
  pInnerDynArr: PString;
  bVariantConversion: boolean;
begin    
  aResult := nil;

  Assert(VarIsType(aSource, varArray or varVariant));
  Assert(VarArrayDimCount(aSource) = 1);

  OuterLBound := VarArrayLowBound(aSource, 1);
  OuterHBound := VarArrayHighBound(aSource, 1);
  OuterCount := {aResultCount} OuterHBound - OuterLBound + 1;

  if OuterCount < 1 then Exit;

  Assert(VarIsType(aSource[0], varArray or varVariant));
  Assert(VarArrayDimCount(aSource[0]) = 1);

  InnerLBound := VarArrayLowBound(aSource[0], 1);
  InnerHBound := VarArrayHighBound(aSource[0], 1);
  InnerCount := InnerHBound - InnerLBound + 1;

  SetLength(aResult, {aResultCount} OuterCount, InnerCount);

  bVariantConversion := NullStrictConvert; // settings to manage how string conversion for Variant is handled.
  NullStrictConvert := False;
  try
    pOuterDynArr := PStrArr(aResult);
    pOuterVarArr := PVariant(VarArrayLock(aSource));
    try
      for i := OuterLBound to OuterHBound do
      begin
        pInnerDynArr := PString(pOuterDynArr^);
        pInnerVarArr := PVariant(VarArrayLock(pOuterVarArr^));
        try
          //System.Variants.DynArrayFromVariant(pOuterDynArr^, pInnerVarArr^, TypeInfo(String));

          for j := InnerLBound to InnerHBound do
          begin
            pInnerDynArr^ := pInnerVarArr^; //implicit conversion to string ... 
            Inc(pInnerDynArr);
            Inc(pInnerVarArr);
          end;
        finally
          VarArrayUnlock(pOuterVarArr^);
        end;
        Inc(pOuterDynArr);
        Inc(pOuterVarArr);
      end;
    finally
      VarArrayUnlock(aSource);
    end;
  finally
    NullStrictConvert := bVariantConversion;
  end;
end;

另一方面,如果内部数组有可能有不同的长度,那么您可以试试这个调整:

type
  TStrArr = array of string;
  PStrArr = ^TStrArr;
  TData = array of TStrArr;

procedure CopyResult(aResultCount: Integer; var aResult: TData; aSource: Variant);
var
  i, j,
  OuterLBound, OuterHBound, OuterCount,
  InnerLBound, InnerHBound, InnerCount: Integer;
  pOuterVarArr, pInnerVarArr: PVariant;
  pOuterDynArr: PStrArr;
  pInnerDynArry: PString;
  bVariantConversion: boolean;
begin    
  aResult := nil;

  Assert(VarIsType(aSource, varArray or varVariant);
  Assert(VarArrayDimCount(aSource) = 1);

  OuterLBound := VarArrayLowBound(aSource, 1);
  OuterHBound := VarArrayHighBound(aSource, 1);
  OuterCount := {aResultCount} OuterHBound - OuterLBound + 1;

  if OuterCount < 1 then Exit;

  SetLength(aResult, {aResultCount} OuterCount);

  bVariantConversion := NullStrictConvert; // settings to manage how string conversion for Variant is handled.
  NullStrictConvert := False;
  try
    pOuterDynArr := PStrArr(aResult);
    pOuterVarArr := PVariant(VarArrayLock(aSource));
    try
      for i := OuterLBound to OuterHBound do
      begin
        pInnerVarArr := PVariant(VarArrayLock(pOuterVarArr^));
        try
          //System.Variants.DynArrayFromVariant(pOuterDynArr^, pInnerVarArr^, TypeInfo(String));

          Assert(VarIsType(pInnerVarArr^, varArray or varVariant);
          Assert(VarArrayDimCount(pInnerVarArr^) = 1);

          InnerLBound := VarArrayLowBound(pInnerVarArr^, 1);
          InnerHBound := VarArrayHighBound(pInnerVarArr^, 1);
          InnerCount := InnerHBound - InnerLBound + 1;

          SetLength(pOuterDynArr^, InnerCount);
          pInnerDynArr := PString(pOuterDynArr^);

          for j := InnerLBound to InnerHBound do
          begin
            pInnerDynArr^ := pInnerVarArr^; //implicit conversion to string ... 
            Inc(pInnerDynArr);
            Inc(pInnerVarArr);
          end;
        finally
          VarArrayUnlock(pOuterVarArr^);
        end;
        Inc(pOuterDynArr);
        Inc(pOuterVarArr);
      end;
    finally
      VarArrayUnlock(aSource);
    end;
  finally
    NullStrictConvert := bVariantConversion;
  end;
end;

编辑:我只测试了相同长度的所有条目的源版本,但它在我自己的部分 [] 免费版本中使用了约 500 万个周期和 Tstopwatch ElapsedTicks 而这个只用了约 2 百万(更像是 1.6 ) 谢谢