2011-09-28 18 views
8

Tenía la impresión de que en .NET casting (no convertir) es muy barato y rápido. Sin embargo, este no parece ser el caso para array. Estoy tratando de hacer un elenco muy simple aquí, tomar una T1 [] y lanzar como T2 []. donde T1: T2.¿Por qué los arrays de arrays (vectores) son tan lentos?

Hay 3 maneras de hacer esto y yo los llamo a la siguiente ::

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

Y creado métodos para hacer esto, por desgracia, C# parece crear un código bastante extraño dependiendo de si esto es genérico o no. (Si su DropCasting genérico utiliza el operador castclass. Y en ambos casos se niega a emitir un operador 'como' cuando T1: T2.

De todos modos, escribí algunos métodos dinámicos y probé algunos resultados sorprendentes (string [] => Object []):.?

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

Dropcasting fue ~ 18 veces más rápido que cualquiera de los operadores de conversión ¿por qué está lanzando tan lento para las matrices para los objetos normales como cadena => objeto, la diferencia fue mucho menor severo.

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

Código de referencia bel ow:

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(); 
     } 
    } 
} 

Editar antes de que alguien pide el mismo es cierto para cosas como int [] -> uint [], que las especificaciones CLR sea echado sin conversión.

+0

El punto era masajear el IL derecho.En un método extremadamente trivial como '() => strings como objeto [];' el compilador descartará el método 'as'. La creación del método dinámico solo se ejecuta una vez en el '.cctor' del programa. Después de eso, cada método es solo el blob de IL. También agregué una "instancia" a cada método dinámico (el parámetro del objeto), solo para evitar la mezcla de thunk al usar un delegado en un método estático. –

+0

sí, me perdí la segunda capa de funcs al leer la primera vez. Entonces eliminé mi comentario. ;) –

+1

Cubierto varias veces, solo puede encontrar la página de inicio: http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-inc-c-part-two -array-covariance.aspx –

Respuesta

0

Porque está fundiendo matrices.

La diferencia entre los 3 fragmentos de código IL es que los dos últimos agregan una operación IsInst y CastClass. Se sabe muy poco sobre los tipos, por lo que el CLR debe verificar si se trata de una operación válida. Esto lleva tiempo.

La pequeña diferencia entre CastClass e IsInst se puede explicar por el hecho de que CastClass realiza una comprobación nula primero y tiene éxito de inmediato si el argumento es nulo.

Sospecho que la ralentización se debe a que estás realizando un fundido entre las matrices. Es posible que sea necesario realizar mucho más trabajo para garantizar que un molde de matriz sea válido. Puede ser necesario mirar cada elemento para ver si se puede convertir al tipo de elemento de destino. Así que supongo que, en lugar de hacer todo esto en el código máquina "en línea", el JIT emite una llamada a una función de validación.

De hecho, si ejecuta un análisis de rendimiento, puede ver que eso es realmente lo que está sucediendo. Casi el 90% del tiempo se gasta en una función llamada "JIT_ChkCastArray".

+0

Esto tiene sentido. Me parece extraño que T1: clase, T2, entonces, el elenco siempre debe ser legal, ¿por qué molestarse en hacer el cheque? –

+0

Supongo que, dado que los arreglos de lanzamiento son relativamente raros, los desarrolladores de JIT no se molestaron en optimizarlo. También es una optimización que los compiladores pueden hacer fácilmente al no emitir las instrucciones CastClass o IsInst en primer lugar. Los recursos de JIT son limitados, por lo que cualquier optimización es relativamente costosa y debe justificarse. –

0

Tiene sentido para mí que la fundición sea (casi) exactamente tan cara como usar el operador as. En ambos escenarios, se debe realizar una comprobación del tiempo de ejecución del tipo de objeto, y se debe determinar si es compatible con el tipo de destino. Se requiere la verificación para permitir que la operación de lanzamiento arroje un InvalidCastException si es necesario.

para decirlo de otra manera, el operador as es una operación de fundición - sólo tiene también la virtud de permitir el reparto falle sin lanzar una excepción (devolviendo nulo). Esto también podría hacerse con una combinación del operador is y un molde, pero eso duplicaría la carga de trabajo.

+0

Me doy cuenta de que está ocurriendo una verificación de tipo, pero ¿por qué es mucho más caro? –

+0

¿Más caro que qué? Sus propios resultados muestran que el casting y el operador 'as' son esencialmente idénticos. (como argumento que deberían ser) Y su primer ejemplo de "control" es una tarea, virtualmente un no-op. (para su "lanzamiento instantáneo", el compilador ya ha hecho todo el trabajo necesario para verificar la validez, por lo que no se aplica el rendimiento en el tiempo de ejecución) –

+0

Lo siento, sé que 'as' y' (T []) 'son versiones, Quería preguntar por qué eran tan lentos en comparación con el caso de control de lanzar cadena-> objeto. –