c# - studio - Diferencias de rendimiento entre las versiones debug y release
debug y release c# (8)
Sí, hay muchas diferencias de rendimiento y realmente se aplican en todo el código. La depuración hace muy poca optimización de rendimiento, y el modo de lanzamiento mucho;
Solo el código que se basa en la constante
DEBUG
puede funcionar de manera diferente con una versión de lanzamiento. Además de eso, no deberías ver ningún problema.
Un ejemplo de código de marco que depende de la constante DEBUG
es el método Debug.Assert()
, que tiene definido el atributo [Conditional("DEBUG)"]
. Esto significa que también depende de la constante DEBUG
y esto no está incluido en la versión de lanzamiento.
Debo admitir que, por lo general, no me he molestado en cambiar entre las configuraciones de Depuración y Liberación en mi programa, y generalmente opté por la configuración de Depuración , incluso cuando los programas se implementan en el lugar de los clientes.
Por lo que sé, la única diferencia entre estas configuraciones, si no lo cambias manualmente, es que Debug tiene la constante DEBUG
definida, y la versión con el código de Optimize activado.
Así que mis preguntas son en realidad dos:
¿Hay muchas diferencias de rendimiento entre estas dos configuraciones? ¿Hay algún tipo específico de código que causará grandes diferencias en el rendimiento aquí, o en realidad no es tan importante?
¿Hay algún tipo de código que funcione bien en la configuración de depuración que pueda fallar en la configuración de la versión , o puede estar seguro de que el código que se prueba y funciona bien en la configuración de la depuración también funcionará bien en la configuración de la versión.
El compilador de C # en sí mismo no altera mucho el IL emitido en la versión Release. Notable es que ya no emite los códigos de operación NOP que le permiten establecer un punto de interrupción en una llave. El grande es el optimizador que está integrado en el compilador JIT. Sé que hace las siguientes optimizaciones:
Método en línea. Una llamada al método es reemplazada por la inyección del código del método. Este es uno de los grandes, hace que los accesores de propiedades sean esencialmente gratuitos.
Asignación de registro de CPU. Las variables locales y los argumentos de los métodos pueden permanecer almacenados en un registro de la CPU sin que se vuelvan a almacenar (o con menos frecuencia) en el marco de la pila. Este es uno grande, notable por hacer que la depuración del código optimizado sea tan difícil. Y dándole un significado a la palabra volátil .
Eliminación de comprobación de índice de matriz. Una optimización importante al trabajar 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 los límites, eliminará la verificación del índice. Uno grande.
Loop desenrollado. Los bucles con cuerpos pequeños se mejoran repitiendo el código hasta 4 veces en el cuerpo y repitiendo menos. Reduce el costo de la sucursal y mejora las opciones de ejecución superscalar del procesador.
Eliminación de código muerto. Una declaración como si (falso) {/ ... /} se elimina completamente. Esto puede ocurrir debido al plegado y alzamiento constante. Otros casos es cuando el compilador JIT puede determinar que el código no tiene ningún efecto secundario posible. Esta optimización es lo que hace que el código de perfil sea tan complicado.
Código de elevación. El código dentro de un bucle que no se ve afectado por el bucle se puede mover fuera del bucle. El optimizador de un compilador de C dedicará mucho más tiempo a encontrar oportunidades para izar. Sin embargo, es una optimización costosa debido al análisis de flujo de datos requerido y el jitter no puede permitirse el tiempo, por lo que solo levanta casos obvios. Obligando a los programadores .NET a escribir un mejor código fuente y elevarse ellos mismos.
Eliminación de la subexpresión común. x = y + 4; z = y + 4; se convierte en z = x; Bastante común en declaraciones como dest [ix + 1] = src [ix + 1]; escrito para facilitar la lectura sin introducir una variable auxiliar. No hay necesidad de comprometer la legibilidad.
Plegado constante. x = 1 + 2; se convierte en x = 3; Este simple ejemplo es captado temprano por el compilador, pero ocurre en el momento JIT cuando otras optimizaciones lo hacen posible.
Copia de propagación. x = a; y = x; se convierte en y = a; Esto ayuda al asignador de registros a tomar mejores decisiones. Es un gran problema en el jitter x86 porque tiene pocos registros con los que trabajar. Tenerlo seleccionado los correctos es crítico para el perf.
Estas son optimizaciones muy importantes que pueden hacer una gran diferencia cuando, por ejemplo, perfila la compilación de depuración de su aplicación y la compara con la compilación de la versión. Sin embargo, eso solo importa realmente cuando el código está en su ruta crítica, el 5 a 10% del código que escribe que realmente afecta al rendimiento de su programa. El optimizador de JIT no es lo suficientemente inteligente como para saber por adelantado lo que es crítico, solo puede aplicar el dial "convertirlo a 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 otros lugares. Leer un archivo, ejecutar una consulta de base de datos, etc. Hacer que el optimizador JIT funcione de manera completamente invisible. Aunque no le importa :)
El optimizador JIT es un código bastante confiable, principalmente porque se ha puesto a prueba millones de veces. Es extremadamente raro tener problemas en la versión de compilación de lanzamiento de su programa. Sin embargo, sucede. Tanto el nerviosismo x64 como el x86 han tenido problemas con las estructuras. El jitter x86 tiene problemas con la consistencia del punto 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 ser truncados cuando se descargan en la memoria.
En mi experiencia, lo peor que ha salido del modo de lanzamiento son los oscuros "errores de lanzamiento". Dado que el IL (lenguaje intermedio) está optimizado en el modo de lanzamiento, existe la posibilidad de errores que no se habrían manifestado en el modo de depuración. Hay otras preguntas de SO que cubren este problema: razones comunes de errores en la versión de lanzamiento que no están presentes en el modo de depuración
Esto me ha sucedido una o dos veces cuando una aplicación de consola simple funcionaría perfectamente bien en el modo de depuración, pero dada la misma entrada exacta, se produciría un error en el modo de lanzamiento. Estos errores son EXTREMADAMENTE difíciles de depurar (por definición, el modo de Lanzamiento, irónicamente).
Esto depende en gran medida de la naturaleza de su aplicación. Si su aplicación tiene una gran cantidad de UI, es probable que no note ninguna diferencia ya que el componente más lento conectado a una computadora moderna es el usuario. Si usa algunas animaciones de UI, es posible que desee probar si puede percibir algún retraso notable cuando se ejecuta en la compilación DEBUG.
Sin embargo, si tiene muchos cálculos pesados en computación, entonces notaría diferencias (podría ser tan alto como 40% como lo mencionó @Pieter, aunque dependerá de la naturaleza de los cálculos).
Es básicamente una compensación de diseño. Si está liberando bajo la compilación DEBUG, entonces si los usuarios experimentan problemas, puede obtener un rastreo más significativo y hacer un diagnóstico mucho más flexible. Al lanzar en la compilación DEBUG, también evita que el optimizador produzca Heisenbugs oscuros.
Yo diría que 1) depende en gran medida de su implementación. Por lo general, la diferencia no es tan grande. Hice muchas mediciones y muchas veces no pude ver una diferencia. Si usas código no administrado, muchos arreglos enormes y cosas así, la diferencia de rendimiento es ligeramente mayor, pero no es un mundo diferente (como en C ++). 2) Por lo general, en el código de publicación se muestran menos errores (mayor tolerancia), por lo tanto, un interruptor debería funcionar bien.
Nunca debe lanzar una compilación de depuración .NET en producción. Puede contener código feo para admitir Editar y continuar o quién sabe qué más. Por lo que sé, esto solo ocurre en VB, no en C # (nota: la publicación original está etiquetada como C #) , pero aún así debería dar razones para hacer una pausa en cuanto a lo que Microsoft cree que pueden hacer con una compilación de depuración. De hecho, antes de .NET 4.0, el código de VB pierde memoria de manera proporcional al número de instancias de objetos con eventos que construye para admitir Editar y continuar. (Aunque se informa que esto se soluciona de acuerdo con https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging , el código generado parece desagradable, crear objetos WeakReference
y agregarlos a una lista estática mientras se mantiene un bloqueo ) ¡Ciertamente no quiero nada de este tipo de soporte de depuración en un entorno de producción!
**Debug Mode:**
Developer use debug mode for debugging the web application on live/local server. Debug mode allow developers to break the execution of program using interrupt 3 and step through the code. Debug mode has below features:
1) Less optimized code
2) Some additional instructions are added to enable the developer to set a breakpoint on every source code line.
3) More memory is used by the source code at runtime.
4) Scripts & images downloaded by webresource.axd are not cached.
5) It has big size, and runs slower.
**Release Mode:**
Developer use release mode for final deployment of source code on live server. Release mode dlls contain optimized code and it is for customers. Release mode has below features:
More optimized code
Some additional instructions are removed and developer can’t set a breakpoint on every source code line.
1) Less memory is used by the source code at runtime.
2) Scripts & images downloaded by webresource.axd are cached.
3) It has small size, and runs fast.
4) Scripts & images downloaded by webresource.axd are cached.
5) It has small size, and runs fast.
Mi experiencia ha sido que las aplicaciones de tamaño mediano o más grande son notablemente más receptivas en una versión de lanzamiento. Pruébelo con su aplicación y vea cómo se siente.
Una cosa que puede morderlo con las compilaciones de la versión es que el código de compilación de depuración a veces puede suprimir las condiciones de carrera y otros errores relacionados con la hebra. El código optimizado puede dar como resultado la reordenación de las instrucciones y una ejecución más rápida puede exacerbar ciertas condiciones de carrera.