El compilador de C# en sí no altera mucho la IL emitida en la compilación Release. Es notable que ya no emite los códigos de operación NOP que le permiten establecer un punto de interrupción en una llave. El más grande es el optimizador que está integrado en el compilador JIT. Sé que hace las siguientes optimizaciones:
Método en línia. Una llamada a método se reemplaza por la inyección del código del método. Este es uno grande, hace que los accesadores de propiedades sean esencialmente gratuitos.
Asignación de registro de la CPU. Las variables locales y los argumentos del método pueden permanecer almacenados en un registro de la CPU sin almacenarse nunca (o con menos frecuencia) en el marco de la pila. Este es uno grande, notable por hacer que el código optimizado para la depuración sea tan difícil. Y dando a la palabra clave volátil un significado.
Eliminación de comprobación del índice de matriz. Una optimización importante cuando se trabaja con matrices (todas las clases de colección .NET usan una matriz internamente). Cuando el compilador JIT puede verificar que un bucle nunca indexa una matriz fuera de límites, eliminará la verificación del índice. Uno grande.
Desenrollado de bucle. Los bucles con cuerpos pequeños se mejoran repitiendo el código hasta 4 veces en el cuerpo y girando menos. Reduce el costo de la sucursal y mejora las opciones de ejecución súper-escalares del procesador.
Eliminación de código muerto. Una declaración como if (falsa) {/ ... /} se elimina por completo. Esto puede ocurrir debido al doblado constante y la alineación. En otros casos, el compilador JIT puede determinar que el código no tiene un efecto secundario posible. Esta optimización es lo que hace que el código de perfiles sea tan complicado.
Código de elevación. El código dentro de un bucle que no se ve afectado por el bucle puede moverse fuera del bucle. El optimizador de un compilador de C pasará mucho más tiempo buscando oportunidades para izar.Sin embargo, es una optimización costosa debido al análisis de flujo de datos requerido y la fluctuación de fase no puede permitirse el tiempo, por lo que solo se alzan casos obvios. Obligando a los programadores de .NET a escribir mejor código fuente y a elevar ellos mismos.
Eliminación de sub-expresión común. x = y + 4; z = y + 4; se convierte en z = x; Muy común en declaraciones como dest [ix + 1] = src [ix + 1]; escrito para facilitar la lectura sin introducir una variable auxiliar. No es necesario comprometer la legibilidad.
Plegado constante. x = 1 + 2; se convierte en x = 3; Este simple ejemplo es captado antes por el compilador, pero ocurre en el momento de JIT cuando otras optimizaciones lo hacen posible.
Copiar propagación. x = a; y = x; se convierte en y = a; Esto ayuda al asignador de registro a tomar mejores decisiones. Es un gran problema en el jitter x86 porque tiene pocos registros para trabajar. Tenerlo seleccionar los correctos es fundamental para perf.
Estos son optimizaciones muy importantes que pueden hacer una gran cantidad de diferencia cuando, por ejemplo, perfilar la versión de depuración de su aplicación y compararlo con la versión de lanzamiento. Sin embargo, eso solo importa cuando el código está en su ruta crítica, el 5 al 10% del código que escribe que en realidad afecta el rendimiento de su programa. El optimizador JIT no es lo suficientemente inteligente como para saber por adelantado lo que es crítico, solo puede aplicar el dial "turn it to once" para todo el código.
El resultado efectivo de estas optimizaciones en el tiempo de ejecución de su programa a menudo se ve afectado por el código que se ejecuta en otro lugar. Leer un archivo, ejecutar una consulta de base de datos, etc. Hacer que el trabajo del optimizador de JIT sea completamente invisible. No le importa :)
El optimizador JIT es un código bastante confiable, sobre todo porque ha sido probado millones de veces. Es extremadamente raro tener problemas en la versión de versión de lanzamiento de su programa. Sucede sin embargo. Tanto el nerviosismo x64 como el x86 han tenido problemas con las estructuras. El jitter x86 tiene problemas con la consistencia de coma flotante, produciendo resultados sutilmente diferentes cuando los intermedios de un cálculo de punto flotante se mantienen en un registro FPU con una precisión de 80 bits en lugar de truncarse cuando se vacían a la memoria.
relacionadas: http://stackoverflow.com/questions/33871181/why-are-async-state-machines-classes-and-not-structs-in-roslyn#comment55507741_33872004 –