2010-10-28 9 views
253

debo admitir, que por lo general no me he molestado conmutación entre el de depuración y de lanzamiento configuraciones en mi programa, y ​​por lo general han optado por ir a la configuración de depuración , incluso cuando los programas se implementan realmente en el lugar del cliente.diferencias de rendimiento entre depuración y liberación construye

Por lo que yo sé, la única diferencia entre estas configuraciones si no lo cambie manualmente es que depuración tienen la constante DEBUG definido, y lanzamiento tener el código Optimizar verificado de.

Así que mi pregunta es en realidad doble:

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

  2. ¿Hay algún tipo de código que se ejecuta bien bajo la configuración depuración que podrían fallar bajo configuración Release, o se puede estar seguro de que el código que se ha probado y funciona bien bajo la configuración depuración se también funciona bien en Configuración de liberación.

+1

relacionadas: http://stackoverflow.com/questions/33871181/why-are-async-state-machines-classes-and-not-structs-in-roslyn#comment55507741_33872004 –

Respuesta

474

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.

+22

No creo que * todas * las colecciones usen matriz (es): 'LinkedList ' no, aunque no se usa con mucha frecuencia. – svick

+0

Creo que CLR configura la FPU con una precisión de 53 bits (combinando dobles de 64 bits), 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 se pueden calcular con esta precisión de 53 bits y solo se truncan cuando se almacenan en la memoria. – Govert

+2

La palabra clave 'volátil' no se aplica a las variables locales almacenadas en un marco de pila. De la documentación en http://msdn.microsoft.com/en-us/library/x13ttww7.aspx: "La palabra clave volátil solo se puede aplicar a los campos de una clase o estructura. Las variables locales no se pueden declarar volátiles". –

22
  1. Sí, hay muchas diferencias de rendimiento y estos realmente se aplican en todo su código. Debug hace muy poca optimización del rendimiento y mucho más el modo de lanzamiento;

  2. Solo el código que se basa en la constante DEBUG puede funcionar de forma 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 el atributo [Conditional("DEBUG)"] definido. Esto significa que también depende de la constante DEBUG y esto no está incluido en la versión de lanzamiento.

+1

Todo esto es cierto, pero ¿alguna vez podrías medir una diferencia? O notar una diferencia al usar un programa? Por supuesto, no quiero alentar a nadie para que libere su software en modo de depuración, pero la pregunta es 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 más alto que las versiones de lanzamiento.Si cree (aunque poco probable) que alguien podría intentar realizar una ingeniería inversa de sus ejecutables, no desea facilitarles la implementación de las versiones de depuración. – jwiscarson

+2

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

3

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 pude ver la diferencia. Si usa código no administrado, muchas matrices enormes y cosas así, la diferencia de rendimiento es un poco mayor, pero no un mundo diferente (como en C++). 2) Por lo general, en el código de versión 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 versión de lanzamiento podría fácilmente no ser más rápida que la depuración. – Richard

5

En mi experiencia, lo peor que ha salido del modo de lanzamiento son los oscuros "errores de liberación". Dado que el IL (lenguaje intermedio) está optimizado en modo Release, existe la posibilidad de que no se hayan manifestado errores en el modo Debug. Hay otras preguntas a fin de que cubran este problema: Common reasons for bugs in release version not present in debug mode

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

+0

Para realizar un seguimiento, aquí hay un artículo que ofrece un ejemplo de un error de versión: http://www.codeproject.com/KB/trace/ReleaseBug.aspx – Roly

+0

Todavía es un problema si la aplicación se prueba y se aprueba con la configuración de depuración , incluso si suprime los errores, si eso hace que la versión de lanzamiento falle durante la implementación. –

10
  • Mi experiencia ha sido que las aplicaciones de tamaño medio o más grandes son notablemente más receptivas en una compilación de versión. Pruébelo con su aplicación y vea cómo se siente.

  • Una cosa que puede morderte con las compilaciones Release es que el código de compilación de Debug a veces puede suprimir las condiciones de carrera y otros errores relacionados con el subprocesamiento. El código optimizado puede dar como resultado el reordenamiento de la instrucción y una ejecución más rápida puede exacerbar ciertas condiciones de carrera.

12

Esto depende en gran medida de la naturaleza de su aplicación. Si su aplicación está cargada de UI, probablemente no notará ninguna diferencia ya que el usuario es el componente más lento conectado a una computadora moderna. Si utiliza algunas animaciones de UI, es posible que desee probar si puede percibir un retraso notable cuando se ejecuta en la creación DEBUG.

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

Es básicamente una compensación de diseño. Si está lanzando bajo DEBUG build, entonces si los usuarios experimentan problemas, puede obtener un traceback más significativo y puede hacer un diagnóstico mucho más flexible. Al liberar en la compilación DEBUG, también evitas que el optimizador produzca Heisenbugs oscuro.

9

Nunca debe lanzar una versión .NET Debug en la producción. Puede contener código feo para admitir Editar-y-Continuar o quién sabe qué más. Hasta donde yo sé, esto ocurre solo en VB no C# (nota: la publicación original está etiquetada C#), pero aún debería dar razón para detenerse en cuanto a lo que Microsoft piensa que pueden hacer con una compilación Debug. De hecho, antes de .NET 4.0, el código VB filtra la memoria proporcional a la cantidad de instancias de objetos con eventos que construye para soportar Edit-and-Continue. (Aunque se informa que esto se corrigió por https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging, el código generado parece desagradable, creando objetos WeakReference y agregándolos a una lista estática mientras manteniendo un bloqueo) Ciertamente no quiero nada de este tipo de soporte de depuración en una producción ¡ambiente!

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: 
    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. 
+0

parece que en el modo de liberación a veces los primeros elementos de una lista no están numerados correctamente. También algunos elementos dentro de la lista están duplicados. :) –

Cuestiones relacionadas