Diferenças de desempenho entre compilações de depuração e lançamento

280

Devo admitir que geralmente não me incomodei em alternar entre as configurações de Depuração e Liberação no meu programa, e geralmente optei por optar pela configuração de Depuração , mesmo quando os programas são realmente implantados no local do cliente.

Até onde eu sei, a única diferença entre essas configurações, se você não as alterar manualmente, é que Debug tem a DEBUGconstante definida e Release tem o código de Otimizar marcado.

Então, minhas perguntas são realmente duplas:

  1. Existem muitas diferenças de desempenho entre essas duas configurações. Existe algum tipo específico de código que causará grandes diferenças no desempenho aqui, ou na verdade não é tão importante?

  2. Existe algum tipo de código que funcione bem na configuração de depuração que pode falhar na configuração da versão ou você pode ter certeza de que o código testado e funcionando bem na configuração de depuração também funcionará bem na configuração da versão.

Øyvind Bråthen
fonte
1
Relacionados: stackoverflow.com/questions/33871181/...
BlueRaja - Danny Pflughoeft

Respostas:

511

O próprio compilador C # não altera muito a IL emitida na compilação Release. Notável é que ele não emite mais os códigos de operação NOP que permitem definir um ponto de interrupção em uma chave. O grande problema é o otimizador incorporado ao compilador JIT. Eu sei que ele faz as seguintes otimizações:

  • Método inlining. Uma chamada de método é substituída pela injeção do código do método. Esse é um grande problema, torna os acessadores de propriedades essencialmente gratuitos.

  • Alocação de registro da CPU. Variáveis ​​locais e argumentos de método podem permanecer armazenados em um registro da CPU sem nunca (ou com menos frequência) serem armazenados de volta no quadro da pilha. Esse é um grande exemplo, por tornar tão difícil o código otimizado para depuração. E dando um significado à palavra-chave volátil .

  • Eliminação de verificação de índice de matriz. Uma otimização importante ao trabalhar com matrizes (todas as classes de coleção do .NET usam uma matriz internamente). Quando o compilador JIT pode verificar se um loop nunca indexa uma matriz fora dos limites, ele elimina a verificação do índice. Um grande.

  • Loop desenrolando. Os loops com corpos pequenos são aprimorados repetindo o código até 4 vezes no corpo e repetindo menos. Reduz o custo da filial e melhora as opções de execução superescalares do processador.

  • Eliminação de código morto. Uma declaração como se (false) {/ ... /} é completamente eliminada. Isso pode ocorrer devido a dobramentos e inlining constantes. Outros casos é onde o compilador JIT pode determinar que o código não tem efeito colateral possível. Essa otimização é o que torna o código de criação de perfis tão complicado.

  • Código de elevação. O código dentro de um loop que não é afetado pelo loop pode ser movido para fora do loop. O otimizador de um compilador C gastará muito mais tempo encontrando oportunidades de içar. No entanto, é uma otimização cara devido à análise de fluxo de dados necessária e a instabilidade não pode permitir o tempo; portanto, apenas elimina casos óbvios. Forçando programadores .NET a escrever melhor código-fonte e a se elevar.

  • Eliminação de subexpressão comum. x = y + 4; z = y + 4; torna-se z = x; Bastante comum em declarações como dest [ix + 1] = src [ix + 1]; escrito para facilitar a leitura sem introduzir uma variável auxiliar. Não há necessidade de comprometer a legibilidade.

  • Dobragem constante. x = 1 + 2; torna-se x = 3; Este exemplo simples é capturado cedo pelo compilador, mas acontece no momento da JIT, quando outras otimizações tornam isso possível.

  • Copiar propagação. x = a; y = x; torna-se y = a; Isso ajuda o alocador de registros a tomar melhores decisões. É um grande problema no jitter x86, pois possui poucos registros para trabalhar. Tê-lo selecionado os corretos é fundamental para o desempenho.

Essas são otimizações muito importantes que podem fazer muita diferença quando, por exemplo, você cria um perfil da versão Debug do seu aplicativo e a compara à versão Release. Isso realmente importa apenas quando o código está no seu caminho crítico, os 5 a 10% do código que você escreve que realmente afetam o desempenho do seu programa. O otimizador de JIT não é inteligente o suficiente para saber de antemão o que é crítico; ele pode aplicar apenas o dial "turn on onze" para todo o código.

O resultado efetivo dessas otimizações no tempo de execução do seu programa geralmente é afetado pelo código executado em outro local. Lendo um arquivo, executando uma consulta dbase, etc. Tornando o trabalho o otimizador de JIT completamente invisível. Mas não se importa :)

O otimizador JIT é um código bastante confiável, principalmente porque foi testado milhões de vezes. É extremamente raro ter problemas na versão de compilação do seu programa. Isso acontece no entanto. Os tremores x64 e x86 tiveram problemas com estruturas. O jitter x86 tem problemas com a consistência do ponto flutuante, produzindo resultados sutilmente diferentes quando os intermediários de um cálculo de ponto flutuante são mantidos em um registro FPU com precisão de 80 bits, em vez de serem truncados quando descarregados na memória.

Hans Passant
fonte
23
Eu não acho que todas as coleções usam array (s): LinkedList<T>não, mesmo que não seja usado com muita frequência.
svick
Acho que o CLR configura a FPU com precisão de 53 bits (correspondendo a dobras de 64 bits), portanto, não deve haver cálculos duplos estendidos de 80 bits para os valores do Float64. No entanto, os cálculos do Float32 podem ser calculados com essa precisão de 53 bits e apenas truncados quando armazenados na memória.
Govert
2
A volatilepalavra-chave não se aplica a variáveis ​​locais armazenadas em um quadro de pilha. Na documentação em msdn.microsoft.com/en-us/library/x13ttww7.aspx : "A palavra-chave volátil pode ser aplicada apenas aos campos de uma classe ou estrutura. Variáveis ​​locais não podem ser declaradas voláteis."
Kris Vandermotten
8
como uma alteração humilde, acho que o que realmente faz a diferença entre Debuge Releaseconstrói nesse sentido é a caixa de seleção "otimizar código", que normalmente está ativada, Releasemas desativada Debug. É apenas para garantir que os leitores não comecem a pensar que existem diferenças invisíveis "mágicas" entre as duas configurações de compilação que vão além do encontrado na página de propriedades do projeto no Visual Studio.
chiccodoro
3
Talvez valha a pena mencionar que praticamente nenhum dos métodos no System.Diagnostics.Debug faz qualquer coisa em uma compilação de depuração. Além disso, as variáveis ​​não são finalizadas com tanta rapidez (veja stackoverflow.com/a/7165380/20553 ).
Martin Brown
23
  1. Sim, existem muitas diferenças de desempenho e elas realmente se aplicam a todo o seu código. A depuração faz muito pouca otimização de desempenho e libera muito o modo;

  2. Somente o código que se baseia na DEBUGconstante pode ter um desempenho diferente com uma compilação de versão. Além disso, você não verá nenhum problema.

Um exemplo de código de estrutura que depende da DEBUGconstante é o Debug.Assert()método, que possui o atributo [Conditional("DEBUG)"]definido. Isso significa que também depende da DEBUGconstante e isso não está incluído na compilação do release.

Pieter van Ginkel
fonte
2
Tudo isso é verdade, mas você poderia medir a diferença? Ou percebe uma diferença ao usar um programa? É claro que não quero incentivar ninguém a lançar seu software no modo de depuração, mas a pergunta era se havia uma enorme diferença de desempenho e não consigo ver isso.
Testalino 28/10/10
2
Também digno de nota é que as versões de depuração se correlacionam com o código fonte original em um grau muito mais alto do que as versões de lançamento. Se você acha (ainda que improvável) que alguém possa tentar fazer engenharia reversa de seus executáveis, não deseje facilitar a implementação de versões de depuração.
jwheron
2
@testalino - Bem, hoje em dia é difícil. Os processadores chegaram tão rápido que o usuário dificilmente espera que um processo execute o código por causa de uma ação do usuário, portanto tudo isso é relativo. No entanto, se você estiver realmente fazendo algum processo demorado, sim, notará. O seguinte código, por exemplo é executado 40% mais lenta em DEBUG: AppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length)).
Pieter van Ginkel
2
Além disso, se você estiver usando asp.nete depurando em vez de liberar, alguns scripts podem ser adicionados à sua página, como: MicrosoftAjax.debug.jsque possui cerca de 7k linhas.
BrunoLM 28/10/2010
13

Isso depende muito da natureza do seu aplicativo. Se o seu aplicativo for pesado na interface do usuário, você provavelmente não notará nenhuma diferença, já que o componente mais lento conectado a um computador moderno é o usuário. Se você usar algumas animações da interface do usuário, poderá testar se consegue perceber algum atraso perceptível ao executar na compilação DEBUG.

No entanto, se você tiver muitos cálculos pesados ​​em computação, perceberá diferenças (pode chegar a 40% como o @Pieter mencionou, embora isso dependa da natureza dos cálculos).

É basicamente uma troca de design. Se você estiver lançando com a versão DEBUG, se os usuários tiverem problemas, poderá obter um retorno mais significativo e um diagnóstico muito mais flexível. Ao liberar na compilação DEBUG, você também evita que o otimizador produza Heisenbugs obscuros .

Lie Ryan
fonte
11
  • Minha experiência foi que aplicativos de tamanho médio ou maior são notavelmente mais responsivos em uma compilação do Release. Experimente o seu aplicativo e veja como ele se sente.

  • Uma coisa que pode incomodá-lo com as compilações de versão é que o código de compilação de depuração às vezes pode suprimir as condições de corrida e outros bugs relacionados a encadeamento. O código otimizado pode resultar na reorganização das instruções e uma execução mais rápida pode agravar certas condições de corrida.

Dan Bryant
fonte
9

Você nunca deve liberar uma compilação do .NET Debug na produção. Pode conter um código feio para dar suporte ao Editar e Continuar ou quem sabe mais o que. Até onde eu sei, isso acontece apenas no VB e não no C # (nota: a postagem original está marcada como C #) , mas ainda deve dar motivos para pausar o que a Microsoft acha que eles podem fazer com uma compilação de depuração. De fato, antes do .NET 4.0, o código VB vaza memória proporcional ao número de instâncias de objetos com eventos que você constrói em suporte ao Edit-and-Continue. (Embora isso seja corrigido por https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging , o código gerado parece desagradável, criando WeakReferenceobjetos e adicionando-os a uma lista estática enquantosegurando uma fechadura ) Eu certamente não quero nenhum deste tipo de suporte de depuração em um ambiente de produção!

Jason Kresowaty
fonte
Lancei versões de depuração muitas vezes e nunca vi um problema. A única diferença, talvez, é que nosso aplicativo no servidor não é um aplicativo da web que suporta muitos usuários. Mas é um aplicativo do lado do servidor com carga de processamento muito alta. Da minha experiência, a diferença entre Debug e Release parece completamente teórica. Eu nunca vi nenhuma diferença prática com nenhum de nossos aplicativos.
Sam Goldberg
5

Na minha experiência, a pior coisa que saiu do modo Release são os obscuros "bugs de lançamento". Como o IL (idioma intermediário) é otimizado no modo Release, existe a possibilidade de erros que não se manifestariam no modo Debug. Existem outras questões de SO que abordam esse problema: Motivos comuns para erros na versão de lançamento não presentes no modo de depuração

Isso aconteceu comigo uma ou duas vezes em que um aplicativo simples de console seria executado perfeitamente no modo Debug, mas, com a mesma entrada exata, ocorreria um erro no modo Release. Esses bugs são EXTREMAMENTE difíceis de depurar (por definição, no modo Release, ironicamente).

Roly
fonte
Para acompanhar, aqui está um artigo que dá um exemplo de um erro de lançamento: codeproject.com/KB/trace/ReleaseBug.aspx
Roly
Ainda é um problema se o aplicativo for testado e aprovado com as configurações de Depuração, mesmo se suprimir erros, se isso fizer com que a compilação da versão falhe durante a implantação.
Øyvind Bråthen
4

Eu diria que 1) depende muito da sua implementação. Normalmente, a diferença não é tão grande. Fiz muitas medições e muitas vezes não via diferença. Se você usar código não gerenciado, muitas matrizes enormes e coisas assim, a diferença de desempenho será um pouco maior, mas não um mundo diferente (como no C ++). 2) Normalmente, no código do release, menos erros são mostrados (tolerância mais alta); portanto, um switch deve funcionar bem.

testalino
fonte
1
Para código vinculado à E / S, uma compilação de release pode facilmente não ser mais rápida que a depuração.
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.
Nandha kumar
fonte
2
parece que, no modo de liberação, às vezes os primeiros elementos de uma lista não são numerados corretamente. Além disso, alguns elementos da lista são duplicados. :)
Gian Paolo