El compilador de C#no optimiza los moldes innecesarios
performance compiler-construction (3)
Hace unos días, mientras escribía una respuesta para esta pregunta aquí en el desbordamiento, me sorprendió un poco el compilador de C #, que no estaba haciendo lo que esperaba que hiciera. Mira lo siguiente a los fragmentos de código:
Primero:
object[] array = new object[1];
for (int i = 0; i < 100000; i++)
{
ICollection<object> col = (ICollection<object>)array;
col.Contains(null);
}
Segundo:
object[] array = new object[1];
for (int i = 0; i < 100000; i++)
{
ICollection<object> col = array;
col.Contains(null);
}
La única diferencia en el código entre los dos fragmentos es el lanzamiento a ICollection <object>. Debido a que object [] implementa explícitamente la interfaz ICollection <object>, esperaba que los dos fragmentos compilaran hasta el mismo IL y, por lo tanto, fueran idénticos. Sin embargo, al ejecutar pruebas de rendimiento en ellos, noté que este último era aproximadamente 6 veces más rápido que el anterior.
Después de comparar la IL de ambos fragmentos de código, noté que ambos métodos eran idénticos, excepto por una instrucción IL de primera clase en el primer fragmento.
Sorprendido por esto ahora me pregunto por qué el compilador de C # no es "inteligente" aquí. Las cosas nunca son tan simples como parece, entonces ¿por qué el compilador de C # es un poco ingenuo aquí?
Esta es una suposición aproximada, pero creo que se trata de la relación del Array con su IEnumerable genérico.
En el .NET Framework versión 2.0, la clase Array implementa las interfaces genéricas System.Collections.Generic.IList, System.Collections.Generic.ICollection y System.Collections.Generic.IEnumerable. Las implementaciones se proporcionan a las matrices en tiempo de ejecución y, por lo tanto, no son visibles para las herramientas de creación de documentación. Como resultado, las interfaces genéricas no aparecen en la sintaxis de declaración para la clase Array, y no hay temas de referencia para los miembros de la interfaz a los que solo se puede acceder mediante la conversión de una matriz al tipo de interfaz genérica (implementaciones de interfaz explícita). La clave a tener en cuenta cuando emite una matriz a una de estas interfaces es que los miembros que agregan, insertan o eliminan elementos generan la excepción NotSupportedException.
Ver el artículo de MSDN .
No está claro si esto se relaciona con .NET 2.0+, pero en este caso especial no tendría sentido por qué el compilador no puede optimizar su expresión si solo es válida en el tiempo de ejecución.
Esto no parece ser más que una oportunidad perdida en el compilador para suprimir el elenco. Funcionará si lo escribe así:
ICollection<object> col = array as ICollection<object>;
lo que sugiere que se vuelve demasiado conservador porque los lanzamientos pueden lanzar excepciones. Sin embargo, funciona cuando realiza la conversión a ICollection no genérico. Concluiría que simplemente lo pasaron por alto.
Hay un problema de optimización más grande en el trabajo aquí, el compilador JIT no aplica la optimización de elevación invariante de bucle. Debería haber reescrito el código así:
object[] array = new object[1];
ICollection<object> col = (ICollection<object>)array;
for (int i = 0; i < 100000; i++)
{
col.Contains(null);
}
Que es una optimización estándar en el generador de códigos C / C ++, por ejemplo. Aun así, el optimizador JIT no puede grabar muchos ciclos en el tipo de análisis requerido para descubrir tales optimizaciones posibles. El ángulo feliz de esto es que el código administrado optimizado todavía es bastante depurable. Y que todavía hay un rol para el programador de C # para escribir código de ejecutante.
Supongo que descubrió un error menor en el optimizador. Existe todo tipo de código de caso especial para arreglos. Gracias por llamar mi atención.