2011-09-28 62 views
8

我的印象是,在.NET铸造(不转换)是非常便宜和快速。但是,这似乎并不适用于数组。我想在这里做一个非常简单的演员,拍一个T1 []并且投射为T2 []。其中T1:T2。为什么铸造数组(向量)这么慢?

有3种方法来做到这一点,我打电话给他们以下::

DropCasting: T2[] array2 = array; 
CastClass: (T2[])array; 
IsInst: array as T2[]; 

我创建的方法来做到这一点,不幸的是,C#似乎取决于如果这创造一些比较奇怪的代码是通用还是不通用。 (如果它的泛型DropCasting使用了castclass操作符,并且在两种情况下都拒绝在T1:T2时发出'as'操作符

无论如何,我写了一些Dynamic方法,并且测试了一些令人惊讶的结果(string [] =>对象[]):?

DropCast : 223ms 
IsInst : 3648ms 
CastClass: 3732ms 

Dropcasting比任铸造运营商的更快〜18倍为什么是铸造这么慢数组 对于像字符串正常对象=>对象,差异少得多严重

DropCast : 386ms 
IsInst : 611ms 
CastClass: 519ms 

基准代码bel流量:

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规格应无需转换投。

+0

重点是按摩IL的权利。在一个非常平凡的方法中,例如'()=> strings as object [];'编译器将放弃'as'方法。动态方法创建仅在程序的'.cctor'处运行一次。之后,每种方法都只是IL blob。此外,我还为每个动态方法(对象参数)添加了一个“实例”,只是为了避免在静态方法上使用委托时的thunk shuffle。 –

+0

是的,我第一次阅读时错过了第二层功能。所以我删除了我的评论。 ;) –

+1

已涵盖多次,只能找到主页:http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two -array-covariance.aspx –

回答

0

因为您正在投射数组。

的IL代码的3个片段之间的区别在于后两种添加Is​​Inst和CastClass操作。对这些类型知之甚少,所以CLR必须检查它是否是有效的操作。这需要时间。

CastClass和IsInst之间的微小差异可以通过一个事实,即CastClass首先进行零检查,并成功立即如果参数为空来解释。

我怀疑减速是因为你在数组之间进行转换。可能需要做更多的工作才能确保数组转换是有效的。可能需要查看每个元素以查看它是否可以转换为目标元素类型。所以我会猜测,JIT不是在'内联'机器代码中完成所有这些工作,而是发出对验证函数的调用。

事实上,如果你运行一个性能分析,可以看到,确实是发生了什么。几乎90%的时间花在一个名为“JIT_ChkCastArray”的函数中。

+0

这很有道理。这对我来说似乎很奇怪,就好像T1:class,T2 then演员必须始终是合法的,那为什么还要干这个检查呢? –

+0

我的猜测是,因为铸造数组相对较少,所以JIT开发人员并不打算对其进行优化。这也是编译器通过首先不发布CastClass或IsInst指令而轻松完成的优化。 JIT资源是有限的,所以任何优化都相对昂贵并且必须是合理的。 –

0

这是有道理的,我认为铸件是(几乎)完全一样的使用as操作昂贵。在这两种情况下,必须对对象类型进行运行时检查,并且必须确定它是否与目标类型兼容。如果需要,该检查是允许投射操作投掷InvalidCastException

换句话说,as运营商转换操作 - 它也具有允许转换失败而不抛出异常(通过返回null)的优点。这也可以通过组合is运算符和一个演员来完成,但这会使工作量增加一倍。

+0

我意识到类型检查正在发生,但为什么它更昂贵? –

+0

比什么更贵?你自己的结果表明铸造和“as”操作符基本相同。 (因为我认为他们应该是)而你的第一个“控制”例子是一个任务 - 实际上是没有任何操作的。 (对于你的“drop cast”,编译器已经完成了检查合法性所需的所有工作,所以没有运行时间性能问题) –

+0

对不起,我知道'as'和'(T [])'都是强制转换,我的意思是问为什么它们比铸造字符串 - >对象的控件更慢。 –