如何有效地将 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 数组复制到我自己的结构中?的主要内容,如果未能解决你的问题,请参考以下文章