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

Posted

技术标签:

【中文标题】如何有效地将 OleVariant 数组复制到我自己的结构中?【英文标题】:How to copy an OleVariant array efficiently to my own structure? 【发布时间】:2021-09-12 20:51:09 【问题描述】:

我正在尝试将 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. 

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

【问题讨论】:

加快代码速度的一种方法是去掉循环中所有重复的VarArray(Low|High)Bound() 调用。您知道数组具有固定大小,因此在进入循环之前计算大小并将其缓存到局部变量中。看起来你已经有了aResultCount 的外部尺寸,所以你真正需要的是内部尺寸,它是VarArrayHighBound(aSource[0], 1) - VarArrayLowBound(aSource[0], 1) + 1(不是VarArrayHighBound(aSource[0], 1) 本身)。 至于数据的实际复制,除非你知道Variant数组在每个元素中保存的数据类型,否则使用VarArrayLock()并不是很有用。是VT_BSTR(OLE 字符串数组)、VT_VARIANT(OLE 变体数组)等吗?您可能无法避免单独复制每个元素,但至少VarArrayLock() 将允许您使用指针算法更快地访问元素,而不是使用委托给VarArrayGet() 并进行边界检查的[] 运算符. VarType() 为外部数组aSource 和每个内部数组aSource[i] 返回什么? 如果您能告诉我们VarType(aSource) 的结果会有所帮助。它决定了您可以通过VarArrayLock 访问哪些数据。 @StijnSanders 我想我现在了解结构。我正在记录我在问题中的进展。 tmyVarType := VarType(aSource); tmyVarType := VarType(aSource[0]); tmyVarType := VarType(aSource[0][0]);前 2 个给我一个 8204,它是一个变体数组,最后一个是 3 个整数。但它会根据我调用 VarType 的每个单元格而相应地发生变化。我想我现在可以看到解决方案了。 @RemyLebeau 是的,每次我在循环中的某个地方点击暂停 iam 以进行绑定检查。我尽量避免使用“std”功能并写我的。问题将被多次编辑......随着我的进步。 【参考方案1】:

此代码中最大的瓶颈是[] 运算符对每个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;

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

【讨论】:

以上是关于如何有效地将 OleVariant 数组复制到我自己的结构中?的主要内容,如果未能解决你的问题,请参考以下文章

如何有效地将数字字符串值分配给整数? [复制]

如何有效地将元素插入数组的任意位置?

如何更有效地将词汇存储在数组中?

在 Angular 中,如何有效地将输入项拆分为数组

如何有效地将项目添加到 Chrome 存储 API 中的数组?

如何正确地将 setState 设置为对象数组? [复制]