2011-09-28 64 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 [])'都是強制轉換,我的意思是問爲什麼它們比鑄造字符串 - >對象的控件更慢。 –