为什么转换数组(向量)这么慢?
本文关键字:向量 转换 数组 为什么 | 更新日期: 2023-09-27 18:10:03
我的印象是,在。net中强制转换(不是转换)是非常便宜和快速的。然而,这似乎不是array的情况。我想在这里做一个非常简单的转换,取T1[]并转换为T2[]。在T1, T2 .
有三种方法可以做到这一点,我将它们命名为::
DropCasting: T2[] array2 = array;
CastClass: (T2[])array;
IsInst: array as T2[];
我创建了方法来做到这一点,不幸的是,c#似乎创建了一些相当奇怪的代码,这取决于这是否是通用的。(如果它的泛型DropCasting使用castclass操作符。在这两种情况下,当T1:T2时拒绝发出'as'操作符。
无论如何,我写了一些动态方法,并测试了一些令人惊讶的结果(string[]=>object[]):
DropCast : 223ms
IsInst : 3648ms
CastClass: 3732ms
cast比两个强制转换操作符都快18倍。为什么对数组的强制转换如此缓慢?对于像string=>object这样的普通对象,差异要小得多。
DropCast : 386ms
IsInst : 611ms
CastClass: 519ms
基准代码如下:
class Program
{
static readonly String[] strings = Enumerable.Range(0, 10).Select(x => x.ToString()).ToArray();
static Func<string[], object[]> Dropcast = new Func<Func<string[], object[]>>(
() =>
{
var method = new DynamicMethod("DropCast", typeof(object[]), new[] { typeof(object), typeof(string[]) },true);
var ilgen = method.GetILGenerator();
ilgen.Emit(OpCodes.Ldarg_1);
ilgen.Emit(OpCodes.Ret);
return method.CreateDelegate(typeof(Func<string[], object[]>)) as Func<string[], object[]>;
})();
static Func<string[], object[]> CastClass = new Func<Func<string[], object[]>>(
() =>
{
var method = new DynamicMethod("CastClass", typeof(object[]), new[] { typeof(object), typeof(string[]) },true);
var ilgen = method.GetILGenerator();
ilgen.Emit(OpCodes.Ldarg_1);
ilgen.Emit(OpCodes.Castclass, typeof(object[]));
ilgen.Emit(OpCodes.Ret);
return method.CreateDelegate(typeof(Func<string[], object[]>)) as Func<string[], object[]>;
})();
static Func<string[], object[]> IsInst = new Func<Func<string[], object[]>>(
() =>
{
var method = new DynamicMethod("IsInst", typeof(object[]), new[] { typeof(object), typeof(string[]) },true);
var ilgen = method.GetILGenerator();
ilgen.Emit(OpCodes.Ldarg_1);
ilgen.Emit(OpCodes.Isinst, typeof(object[]));
ilgen.Emit(OpCodes.Ret);
return method.CreateDelegate(typeof(Func<string[], object[]>)) as Func<string[], object[]>;
})();
static Func<string[], object[]>[] Tests = new Func<string[], object[]>[]{
Dropcast,
IsInst,
CastClass
};
static void Main(string[] args)
{
int maxMethodLength = Tests.Select(x => GetMethodName(x.Method).Length).Max();
RunTests(1, false, maxMethodLength);
RunTests(100000000, true, maxMethodLength);
}
static string GetMethodName(MethodInfo method)
{
return method.IsGenericMethod ?
string.Format(@"{0}<{1}>", method.Name, string.Join<Type>(",", method.GetGenericArguments())) : method.Name;
}
static void RunTests(int count, bool displayResults, int maxLength)
{
foreach (var action in Tests)
{
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
action(strings);
}
sw.Stop();
if (displayResults)
{
Console.WriteLine("{0}: {1}ms", GetMethodName(action.Method).PadRight(maxLength),
((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
}
对于int[]->uint[]这样的东西,clr规范应该在没有转换的情况下进行强制转换。
因为您正在强制转换数组。
这3段IL代码的区别在于后两者添加了一个IsInst和一个CastClass操作。对于这些类型所知甚少,因此CLR必须检查它是否是一个有效的操作。这需要时间。
CastClass和IsInst之间的细微差别可以用以下事实来解释:CastClass首先进行空检查,如果参数为空则立即成功。
我怀疑减速是因为你在数组之间进行了强制转换。可能需要做更多的工作来确保数组强制转换是有效的。可能有必要查看每个元素,以确定是否可以将其强制转换为目标元素类型。所以我猜,不是在"内联"机器码中完成所有这些,而是JIT发出对验证函数的调用。
事实上,如果您运行性能分析,您可以看到确实发生了什么。几乎90%的时间都花在一个名为"JIT_ChkCastArray"的函数上。
对我来说,强制转换(几乎)和使用as
运算符一样昂贵是有意义的。在这两种情况下,都必须对对象的类型进行运行时检查,并且必须确定它是否与目标类型兼容。如果需要,需要检查以允许强制转换操作抛出InvalidCastException
。
换句话说,as
操作符是强制转换操作——它还具有允许强制转换失败而不抛出异常(通过返回null)的优点。这也可以通过is
操作符和强制转换的组合来实现,但是这会使工作负载加倍。