I was under the impression that in .NET casting (not converting) is very cheap and fast. However, this does not seem to be the case for array. I'm trying to do a very simple cast here, take a T1[] and cast as T2[]. where T1:T2.
我的印象是在.NET中(非转换)非常便宜且快速。但是,这似乎不是阵列的情况。我想在这里做一个非常简单的演员,拿一个T1 []并施放为T2 []。其中T1:T2。
There are 3 ways to do this and I'm calling them the following::
有3种方法可以做到这一点,我称之为以下::
DropCasting: T2[] array2 = array;
CastClass: (T2[])array;
IsInst: array as T2[];
And I created methods to do this, unfortunately, C# seems to create some rather strange code depending on if this is generic or not. (If its generic DropCasting uses the castclass operator. And in both cases refuse to emit an 'as' operator when T1:T2.
我创建了这样做的方法,不幸的是,C#似乎创建了一些相当奇怪的代码,具体取决于它是否是通用的。 (如果它的通用DropCasting使用了castclass运算符。在两种情况下,当T1:T2时拒绝发出'as'运算符。
Anyway, I wrote some Dynamic methods and I tested it to some surprising results (string[]=>object[]):
无论如何,我写了一些动态方法,我测试了一些令人惊讶的结果(string [] => object []):
DropCast : 223ms
IsInst : 3648ms
CastClass: 3732ms
Dropcasting was ~18 times faster than either of the cast operators. Why is casting so slow for arrays? For normal objects like string=>object, the difference was much less severe.
垂直投射比任何一个投射操作员快约18倍。为什么阵列的播放速度如此之慢?对于像string => object这样的普通对象,差异要小得多。
DropCast : 386ms
IsInst : 611ms
CastClass: 519ms
Benchmark code below:
基准代码如下:
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();
}
}
}
Edit before anyone asks the same hold true for things like int[]->uint[] which the clr specs should be cast without conversion.
编辑之前任何人都要求int [] - > uint []这样的事情,clr specs应该在没有转换的情况下进行转换。
2 个解决方案
#1
0
Because you are casting arrays.
因为你正在构建数组。
The difference between the 3 snippets of IL code is that the latter two add an IsInst and a CastClass operation. Very little is known about the types, so the CLR has to check whether it is a valid operation. This takes time.
IL代码的3个片段之间的区别在于后两个添加了IsInst和CastClass操作。关于类型知之甚少,因此CLR必须检查它是否是有效的操作。这需要时间。
The slight difference between CastClass and IsInst can be explained by the fact that CastClass does a null check first and succeeds right away if the argument is null.
CastClass和IsInst之间的细微差别可以解释为CastClass首先进行空检查,如果参数为null则立即成功。
I suspect that the slowdown is because you are casting between arrays. A lot more work may need to be done to make sure that an array cast is valid. It may be necessary to look at each element to see if it can be cast to the target element type. So I would guess that, rather than do all this in 'inline' machine code, the JIT emits a call to a validation function.
我怀疑减速是因为你在数组之间进行投射。可能需要做更多工作才能确保数组转换有效。可能需要查看每个元素以查看它是否可以强制转换为目标元素类型。所以我猜想,JIT会发出对验证函数的调用,而不是在“内联”机器代码中执行所有操作。
In fact, if you run a performance analysis, you can see that is indeed what is happening. Almost 90% of the time is spent in a function called "JIT_ChkCastArray".
实际上,如果您运行性能分析,您可以看到确实发生了什么。几乎90%的时间花在一个名为“JIT_ChkCastArray”的函数上。
#2
0
It makes sense to me that casting would be (almost) exactly as expensive as using the as
operator. In both scenarios, a runtime check on the type of the object must be made, and it must be determined whether it is compatible with the target type. The check is required to allow the cast operation to throw an InvalidCastException
if necessary.
对我来说,铸造(几乎)与使用as运算符一样昂贵是有道理的。在这两种情况下,都必须对对象的类型进行运行时检查,并且必须确定它是否与目标类型兼容。如果需要,需要进行检查以允许强制转换操作抛出InvalidCastException。
To put it another way, the as
operator is a cast operation -- it just also has the virtue of allowing the cast to fail without throwing an exception (by returning null). This could also be done with a combination of the is
operator and a cast, but that would double the workload.
换句话说,as运算符是一个强制转换操作 - 它还具有允许强制转换失败而不抛出异常(通过返回null)的优点。这也可以通过运算符和强制转换的组合来完成,但这会使工作量加倍。
#1
0
Because you are casting arrays.
因为你正在构建数组。
The difference between the 3 snippets of IL code is that the latter two add an IsInst and a CastClass operation. Very little is known about the types, so the CLR has to check whether it is a valid operation. This takes time.
IL代码的3个片段之间的区别在于后两个添加了IsInst和CastClass操作。关于类型知之甚少,因此CLR必须检查它是否是有效的操作。这需要时间。
The slight difference between CastClass and IsInst can be explained by the fact that CastClass does a null check first and succeeds right away if the argument is null.
CastClass和IsInst之间的细微差别可以解释为CastClass首先进行空检查,如果参数为null则立即成功。
I suspect that the slowdown is because you are casting between arrays. A lot more work may need to be done to make sure that an array cast is valid. It may be necessary to look at each element to see if it can be cast to the target element type. So I would guess that, rather than do all this in 'inline' machine code, the JIT emits a call to a validation function.
我怀疑减速是因为你在数组之间进行投射。可能需要做更多工作才能确保数组转换有效。可能需要查看每个元素以查看它是否可以强制转换为目标元素类型。所以我猜想,JIT会发出对验证函数的调用,而不是在“内联”机器代码中执行所有操作。
In fact, if you run a performance analysis, you can see that is indeed what is happening. Almost 90% of the time is spent in a function called "JIT_ChkCastArray".
实际上,如果您运行性能分析,您可以看到确实发生了什么。几乎90%的时间花在一个名为“JIT_ChkCastArray”的函数上。
#2
0
It makes sense to me that casting would be (almost) exactly as expensive as using the as
operator. In both scenarios, a runtime check on the type of the object must be made, and it must be determined whether it is compatible with the target type. The check is required to allow the cast operation to throw an InvalidCastException
if necessary.
对我来说,铸造(几乎)与使用as运算符一样昂贵是有道理的。在这两种情况下,都必须对对象的类型进行运行时检查,并且必须确定它是否与目标类型兼容。如果需要,需要进行检查以允许强制转换操作抛出InvalidCastException。
To put it another way, the as
operator is a cast operation -- it just also has the virtue of allowing the cast to fail without throwing an exception (by returning null). This could also be done with a combination of the is
operator and a cast, but that would double the workload.
换句话说,as运算符是一个强制转换操作 - 它还具有允许强制转换失败而不抛出异常(通过返回null)的优点。这也可以通过运算符和强制转换的组合来完成,但这会使工作量加倍。