Diferencias de rendimiento entre las versiones de depuración y lanzamiento


280

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 ​​por lo general he optado por la configuración de Depuración , incluso cuando los programas se implementan realmente en el lugar del cliente.

Hasta donde sé, la única diferencia entre estas configuraciones si no las cambia manualmente es que Debug tiene DEBUGdefinida la constante, y Release tiene marcado el código de Optimize .

Entonces mis preguntas son en realidad dobles:

  1. ¿Hay muchas diferencias de rendimiento entre estas dos configuraciones? ¿Existe algún tipo específico de código que causará grandes diferencias en el rendimiento aquí, o en realidad no es tan importante?

  2. ¿Hay algún tipo de código que se ejecutará bien en la configuración de depuración que podría fallar en la configuración de lanzamiento , o puede estar seguro de que el código que se prueba y funciona bien en la depuración configuración de también funcionará bien en la configuración de lanzamiento?


Respuestas:


511

El compilador de C # en sí no altera mucho la IL emitida en la versión de lanzamiento. 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 rizada. El más grande es el optimizador integrado en el compilador JIT. Sé que hace las siguientes optimizaciones:

  • Método en línea. Una llamada al método se reemplaza por la inyección del código del método. Este es uno grande, hace que los accesos de propiedad sean esencialmente gratuitos.

  • Asignación de registro de CPU. Las variables locales y los argumentos del método pueden permanecer almacenados en un registro de CPU sin volver a almacenarse (o con menos frecuencia) en el marco de la pila. Este es uno grande, notable por hacer que la depuración de código optimizado sea tan difícil. Y dando un significado a la palabra clave volátil .

  • Eliminación de comprobación de í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 los límites, eliminará la verificación del índice. Uno grande.

  • Lazo se desenrolla. Los bucles con cuerpos pequeños se mejoran repitiendo el código hasta 4 veces en el cuerpo y haciendo bucles menos. Reduce el costo de sucursal y mejora las opciones de ejecución súper escalar del procesador.

  • Eliminación de código muerto. Una declaración como if (false) {/ ... /} se elimina por completo. Esto puede ocurrir debido al constante plegado y alineado. En otros casos, 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 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 el jitter no puede permitirse el tiempo, por lo que solo levanta casos obvios. Obligar a los programadores de .NET a escribir un mejor código fuente y izar ellos mismos.

  • Eliminación de 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 es necesario comprometer la legibilidad.

  • Plegado constante. x = 1 + 2; se convierte en x = 3; Este simple ejemplo es captado temprano por el compilador, pero sucede 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 para trabajar. Hacer que seleccione los correctos es fundamental para el rendimiento.

Estas son optimizaciones muy importantes que pueden marcar 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 lanzamiento. Sin embargo, eso realmente solo importa cuando el código está en su ruta crítica, el 5 al 10% del código que escribe que realmente 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 "convertirlo en 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. Lectura de un archivo, ejecución de una consulta dbase, etc. Haciendo que el trabajo del optimizador JIT sea 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 las fluctuaciones x64 como las x86 han tenido problemas con las estructuras. La fluctuación de fase x86 tiene problemas con la consistencia de coma flotante, produciendo resultados sutilmente diferentes cuando los intermedios de un cálculo de coma flotante se mantienen en un registro FPU con una precisión de 80 bits en lugar de truncarse cuando se vacía en la memoria.


23
No creo que todas las colecciones usen matriz (s): LinkedList<T>no, aunque no se use con mucha frecuencia.
svick

Creo que el CLR configura la FPU con una precisión de 53 bits (coincidencia de dobles de 64 bits de ancho), por lo que no debería haber cálculos dobles extendidos de 80 bits para los valores de Float64. Sin embargo, los cálculos de Float32 pueden calcularse con esta precisión de 53 bits y solo truncarse cuando se almacenan en la memoria.
Govert

2
La volatilepalabra clave no se aplica a las variables locales almacenadas en un marco de pila. De la documentación en msdn.microsoft.com/en-us/library/x13ttww7.aspx : "La palabra clave volátil solo puede aplicarse a campos de una clase o estructura. Las variables locales no pueden declararse volátiles".
Kris Vandermotten

8
como enmienda humilde, supongo que lo que realmente hace la diferencia entre Debugy Releasese basa en este sentido es la casilla de verificación "código de optimizar" que normalmente es el de Release, pero fuera de Debug. Es solo para asegurarse de que los lectores no empiecen a pensar que existen diferencias "mágicas" e invisibles entre las dos configuraciones de compilación que van más allá de lo que se encuentra en la página de propiedades del proyecto en Visual Studio.
chiccodoro

3
Quizás valga la pena mencionar que prácticamente ninguno de los métodos en System.Diagnostics.Debug hace nada en una compilación de depuración. Además, las variables no se finalizan con tanta rapidez ( stackoverflow.com/a/7165380/20553 ).
Martin Brown

23
  1. Sí, hay muchas diferencias de rendimiento y estas realmente se aplican a todo el código. La depuración optimiza muy poco el rendimiento y libera mucho el modo;

  2. Solo el código que se basa en la DEBUGconstante 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 marco que depende de la DEBUGconstante es el Debug.Assert()método, que tiene el atributo [Conditional("DEBUG)"]definido. Esto significa que también depende de la DEBUGconstante y esto no está incluido en la versión de lanzamiento.


2
Todo esto es cierto, pero ¿podrías medir alguna vez la diferencia? ¿O nota una diferencia mientras usa un programa? Por supuesto, no quiero alentar a nadie a lanzar su software en modo de depuración, pero la pregunta era si hay una gran diferencia de rendimiento y no puedo ver eso.
testalino

2
También vale la pena señalar que las versiones de depuración se correlacionan con el código fuente original en un grado mucho mayor que las versiones de lanzamiento. Si cree (por improbable que sea) que alguien podría intentar aplicar ingeniería inversa a sus ejecutables, no desea que sea más fácil implementar versiones de depuración.
jwheron

2
@testalino - Bueno, en estos días es difícil. Los procesadores han llegado tan rápido que el usuario apenas espera que un proceso realmente ejecute código debido a una acción del usuario, por lo que todo esto es relativo. Sin embargo, si realmente está haciendo un proceso largo, sí, lo notará. El código siguiente ejemplo se ejecuta 40% más lenta en DEBUG: AppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length)).
Pieter van Ginkel

2
Además, si está asp.netactivado y utiliza la depuración en lugar de liberar, se pueden agregar algunos scripts en su página, como: MicrosoftAjax.debug.jsque tiene aproximadamente 7k líneas.
BrunoLM

13

Esto depende en gran medida de la naturaleza de su aplicación. Si su aplicación tiene mucha IU, probablemente no notará ninguna diferencia, ya que el componente más lento conectado a una computadora moderna es el usuario. Si usa algunas animaciones de la interfaz de usuario, es posible que desee probar si puede percibir algún retraso notable cuando se ejecuta en DEBUG build.

Sin embargo, si tiene muchos cálculos de cálculo pesado, entonces notará diferencias (podría ser tan alto como 40% como lo mencionó @Pieter, aunque dependería de la naturaleza de los cálculos).

Básicamente es una compensación de diseño. Si está lanzando bajo DEBUG build, entonces si los usuarios experimentan problemas, puede obtener un rastreo más significativo y puede hacer un diagnóstico mucho más flexible. Al lanzar en DEBUG build, también evitas que el optimizador produzca Heisenbugs oscuros .


11
  • Mi experiencia ha sido que las aplicaciones medianas o grandes 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 morderte con las versiones de lanzamiento es que el código de compilación de depuración a veces puede suprimir las condiciones de carrera y otros errores relacionados con subprocesos. El código optimizado puede resultar en un reordenamiento de instrucciones y una ejecución más rápida puede exacerbar ciertas condiciones de carrera.


9

Nunca debe lanzar una compilación de depuración de .NET en producción. Puede contener código feo para admitir Editar y continuar o quién sabe qué más. Hasta donde sé, esto ocurre solo en VB, no en C # (nota: la publicación original está etiquetada con C #) , pero aún así debería dar motivos para detenerse 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 VB pierde memoria proporcionalmente a la cantidad de instancias de objetos con eventos que construye para admitir Editar y continuar. (Aunque se informa que esto se corrige según https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging , el código generado parece desagradable, crea WeakReferenceobjetos y los agrega a una lista estática mientrasmanteniendo un bloqueo ) ¡Ciertamente no quiero nada de este tipo de soporte de depuración en un entorno de producción!


He lanzado versiones de depuración muchas veces, y nunca he visto un problema. Quizás la única diferencia es que nuestra aplicación del lado del servidor no es una aplicación web que admita muchos usuarios. Pero es una aplicación del lado del servidor con una carga de procesamiento muy alta. Desde mi experiencia, la diferencia entre Debug y Release parece completamente teórica. Nunca he visto ninguna diferencia práctica con ninguna de nuestras aplicaciones.
Sam Goldberg

5

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 modo Release, existe la posibilidad de errores que no se habrían manifestado en modo Debug. Hay otras preguntas SO que cubren este problema: Razones comunes para errores en la versión de lanzamiento que no están presentes en el modo de depuración

Esto me sucedió una o dos veces cuando una aplicación de consola simple funcionaría perfectamente bien en modo de depuración, pero dada la misma entrada, se produciría un error en el modo de lanzamiento. Estos errores son EXTREMADAMENTE difíciles de depurar (por definición del modo Release, irónicamente).


Para seguir, aquí hay un artículo que da un ejemplo de un error de lanzamiento: codeproject.com/KB/trace/ReleaseBug.aspx
Roly

Aún así, es un problema si la aplicación se prueba y aprueba con la configuración de depuración, incluso si suprime los errores, si eso hace que la compilación de la versión falle durante la implementación.
Øyvind Bråthen

4

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, a menudo, no podía ver la diferencia. Si usa código no administrado, muchas matrices enormes y cosas así, la diferencia de rendimiento es ligeramente mayor, pero no un mundo diferente (como en C ++). 2) Por lo general, en el código de lanzamiento se muestran menos errores (mayor tolerancia), por lo tanto, un interruptor debería funcionar bien.


1
Para el código que está vinculado a IO, una compilación de lanzamiento podría no ser más rápida que la depuración.
Richard

0
    **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:
   1) More optimized code
   2) Some additional instructions are removed and developer cant set a breakpoint on every source code line.
   3) Less memory is used by the source code at runtime.
   4) Scripts & images downloaded by webresource.axd are cached.
   5) It has small size, and runs fast.

2
Parece que en el modo de lanzamiento a veces los primeros elementos de una lista no están numerados correctamente. También algunos elementos dentro de la lista están duplicados. :)
Gian Paolo
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.