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 DEBUG
constante definida e Release tem o código de Otimizar marcado.
Então, minhas perguntas são realmente duplas:
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?
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.
fonte
Respostas:
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.
fonte
LinkedList<T>
não, mesmo que não seja usado com muita frequência.volatile
palavra-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."Debug
eRelease
constrói nesse sentido é a caixa de seleção "otimizar código", que normalmente está ativada,Release
mas desativadaDebug
. É 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.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;
Somente o código que se baseia na
DEBUG
constante 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
DEBUG
constante é oDebug.Assert()
método, que possui o atributo[Conditional("DEBUG)"]
definido. Isso significa que também depende daDEBUG
constante e isso não está incluído na compilação do release.fonte
DEBUG
:AppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length))
.asp.net
e depurando em vez de liberar, alguns scripts podem ser adicionados à sua página, como:MicrosoftAjax.debug.js
que possui cerca de 7k linhas.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 .
fonte
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.
fonte
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
WeakReference
objetos 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!fonte
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).
fonte
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.
fonte
fonte