Differenze di prestazioni tra build di debug e versioni di rilascio


280

Devo ammettere che di solito non mi sono preoccupato di passare tra le configurazioni Debug e Release nel mio programma e di solito ho optato per la configurazione di Debug , anche quando i programmi sono effettivamente distribuiti sul posto dei clienti.

Per quanto ne so, l'unica differenza tra queste configurazioni se non la si modifica manualmente è che Debug ha la DEBUGcostante definita e Release ha il codice Optimize verificato.

Quindi le mie domande sono in realtà doppie:

  1. Ci sono molte differenze di prestazioni tra queste due configurazioni. Esiste un tipo specifico di codice che causerà grandi differenze nelle prestazioni qui, o in realtà non è così importante?

  2. Esiste un tipo di codice che verrà eseguito correttamente nella configurazione di debug che potrebbe non riuscire nella configurazione di rilascio o si può essere certi che il codice testato e funzionante correttamente nella configurazione di debug funzionerà bene anche nella configurazione di rilascio.


Risposte:


511

Il compilatore C # stesso non modifica molto l'IL emesso nella build di Release. È da notare che non emette più i codici operativi NOP che consentono di impostare un punto di interruzione su una parentesi graffa. Il grande è l'ottimizzatore integrato nel compilatore JIT. So che apporta le seguenti ottimizzazioni:

  • Metodo in linea. Una chiamata di metodo viene sostituita dall'iniezione del codice del metodo. Questo è grande, rende gli accessori di proprietà essenzialmente gratuiti.

  • Allocazione del registro CPU. Le variabili locali e gli argomenti del metodo possono rimanere memorizzati in un registro CPU senza mai (o meno frequentemente) essere memorizzati nel frame dello stack. Questo è grande, notevole per rendere il debug del codice ottimizzato così difficile. E dando un significato alla parola chiave volatile .

  • Indice array che verifica l'eliminazione. Un'ottimizzazione importante quando si lavora con array (tutte le classi di raccolte .NET utilizzano un array internamente). Quando il compilatore JIT può verificare che un ciclo non indicizzi mai un array fuori limite, eliminerà il controllo dell'indice. Grande.

  • Svolgimento loop. I loop con piccoli corpi vengono migliorati ripetendo il codice fino a 4 volte nel corpo e eseguendo un ciclo inferiore. Riduce i costi di filiale e migliora le opzioni di esecuzione super-scalare del processore.

  • Eliminazione del codice morto. Un'affermazione come se (false) {/ ... /} viene completamente eliminato. Ciò può verificarsi a causa della costante piegatura e allineamento. Altri casi in cui il compilatore JIT può determinare che il codice non ha alcun effetto collaterale. Questa ottimizzazione è ciò che rende il codice di profilazione così complicato.

  • Sollevamento del codice. Il codice all'interno di un loop che non è interessato dal loop può essere spostato fuori dal loop. L'ottimizzatore di un compilatore C dedicherà molto più tempo alla ricerca di opportunità da sollevare. È tuttavia un'ottimizzazione costosa a causa dell'analisi del flusso di dati richiesta e il jitter non può permettersi il tempo, quindi solleva solo casi ovvi. Costringere i programmatori .NET a scrivere codice sorgente migliore e issarsi.

  • Eliminazione comune delle sottoespressioni. x = y + 4; z = y + 4; diventa z = x; Abbastanza comune in affermazioni come dest [ix + 1] = src [ix + 1]; scritto per leggibilità senza introdurre una variabile helper. Non è necessario compromettere la leggibilità.

  • Piegatura costante. x = 1 + 2; diventa x = 3; Questo semplice esempio viene colto in anticipo dal compilatore, ma si verifica al momento JIT quando altre ottimizzazioni lo rendono possibile.

  • Copia propagazione. x = a; y = x; diventa y = a; Questo aiuta l'allocatore del registro a prendere decisioni migliori. È un grosso problema nel jitter x86 perché ha pochi registri con cui lavorare. Avere selezionare quelli giusti è fondamentale per perf.

Queste sono ottimizzazioni molto importanti che possono fare una grande differenza quando, ad esempio, si profila la build di debug della propria app e la si confronta con la build di rilascio. Ciò importa davvero solo quando il codice si trova sul tuo percorso critico, dal 5 al 10% del codice che scrivi che influisce effettivamente sul perf del tuo programma. L'ottimizzatore JIT non è abbastanza intelligente da sapere in anticipo ciò che è critico, può solo applicare il quadrante "girare a undici" per tutto il codice.

Il risultato efficace di queste ottimizzazioni sul tempo di esecuzione del programma è spesso influenzato dal codice che viene eseguito altrove. Lettura di un file, esecuzione di una query dbase, ecc. Rendere il lavoro dell'ottimizzatore JIT completamente invisibile. Non importa però :)

L'ottimizzatore JIT è un codice abbastanza affidabile, soprattutto perché è stato messo alla prova milioni di volte. È estremamente raro riscontrare problemi nella versione build di rilascio del programma. Succede comunque. Sia il jitter x64 che x86 hanno avuto problemi con le strutture. Il jitter x86 ha problemi con la coerenza in virgola mobile, producendo risultati leggermente diversi quando gli intermedi di un calcolo in virgola mobile vengono mantenuti in un registro FPU con precisione a 80 bit invece di essere troncati quando vengono scaricati nella memoria.


23
Non credo che tutte le raccolte utilizzino array (s): LinkedList<T>no, anche se non viene usata molto spesso.
svick,

Penso che il CLR configura la FPU con precisione a 53 bit (corrispondente a doppie larghe a 64 bit), quindi non dovrebbero esserci calcoli doppi estesi a 80 bit per i valori Float64. Tuttavia, i calcoli Float32 potrebbero essere calcolati con questa precisione a 53 bit e troncati solo se archiviati in memoria.
Govert,

2
La volatileparola chiave non si applica alle variabili locali memorizzate in un frame dello stack. Dalla documentazione su msdn.microsoft.com/en-us/library/x13ttww7.aspx : "La parola chiave volatile può essere applicata solo ai campi di una classe o struttura. Le variabili locali non possono essere dichiarate volatili."
Kris Vandermotten

8
come modesto emendamento, suppongo che ciò che realmente fa la differenza Debuge si Releasebasa su questo aspetto è la casella di controllo "Ottimizza codice" che è normalmente attiva per Releasema disattivata per Debug. È solo per assicurarsi che i lettori non inizino a pensare che ci siano differenze "magiche" e invisibili tra le due configurazioni di build che vanno oltre ciò che si trova nella pagina delle proprietà del progetto in Visual Studio.
Chiccodoro,

3
Forse vale la pena ricordare che praticamente nessuno dei metodi su System.Diagnostics.Debug fa qualcosa in una build di debug. Inoltre, le variabili non vengono finalizzate abbastanza rapidamente (vedi stackoverflow.com/a/7165380/20553 ).
Martin Brown,

23
  1. Sì, ci sono molte differenze di prestazioni e queste si applicano davvero a tutto il codice. Il debug fa pochissima ottimizzazione delle prestazioni e la modalità di rilascio molto;

  2. Solo un codice che si basa sulla DEBUGcostante può funzionare diversamente con una build di rilascio. Oltre a ciò, non dovresti vedere alcun problema.

Un esempio di codice framework che dipende dalla DEBUGcostante è il Debug.Assert()metodo, che ha l'attributo [Conditional("DEBUG)"]definito. Ciò significa che dipende anche dalla DEBUGcostante e questo non è incluso nella build di rilascio.


2
Questo è tutto vero, ma potresti mai misurare una differenza? O noti una differenza durante l'utilizzo di un programma? Ovviamente non voglio incoraggiare nessuno a rilasciare il proprio software in modalità debug, ma la domanda era se c'è un'enorme differenza di prestazioni e non riesco a vederlo.
testalino,

2
Vale anche la pena notare che le versioni di debug sono correlate al codice sorgente originale in misura molto maggiore rispetto alle versioni di rilascio. Se ritieni (per quanto improbabile) che qualcuno possa provare a decodificare i tuoi eseguibili, non vorrai renderli più facili distribuendo versioni di debug.
jwheron,

2
@testalino - Beh, in questi giorni è difficile. I processori sono diventati così veloci che l'utente non aspetta quasi che un processo esegua effettivamente il codice a causa di un'azione dell'utente, quindi è tutto relativo. Tuttavia, se stai effettivamente facendo un lungo processo, sì, lo noterai. Il codice seguente ad esempio viene eseguito il 40% più lento sotto DEBUG: AppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length)).
Pieter van Ginkel,

2
Inoltre, se sei attivo asp.nete usi il debug invece del rilascio, alcuni script potrebbero essere aggiunti alla tua pagina, come ad esempio: MicrosoftAjax.debug.jsche ha circa 7k righe.
BrunoLM,

13

Questo dipende fortemente dalla natura della tua applicazione. Se l'applicazione è pesante per l'interfaccia utente, probabilmente non noterai alcuna differenza poiché l'utente è il componente più lento collegato a un computer moderno. Se si utilizzano alcune animazioni dell'interfaccia utente, è possibile verificare se è possibile percepire un ritardo evidente durante l'esecuzione nella build DEBUG.

Tuttavia, se si dispone di molti calcoli pesanti per il calcolo, si noterebbero differenze (potrebbero arrivare al 40% come menzionato da @Pieter, sebbene dipenda dalla natura dei calcoli).

È fondamentalmente un compromesso di progettazione. Se stai rilasciando sotto build DEBUG, allora se gli utenti riscontrano problemi, puoi ottenere un traceback più significativo e puoi fare una diagnostica molto più flessibile. Rilasciando nella build DEBUG, eviti anche che l'ottimizzatore produca Heisenbugs oscuri .


11
  • La mia esperienza è stata che le applicazioni di medie o grandi dimensioni sono notevolmente più reattive in una build di rilascio. Provalo con la tua applicazione e guarda come ci si sente.

  • Una cosa che può morderti con le build di Release è che il codice di build di Debug a volte può sopprimere le condizioni di gara e altri bug relativi al threading. Il codice ottimizzato può comportare il riordino delle istruzioni e un'esecuzione più rapida può aggravare determinate condizioni di gara.


9

Non si dovrebbe mai rilasciare una build di debug .NET in produzione. Può contenere brutti codici per supportare Modifica e continua o chissà cos'altro. Per quanto ne so, questo accade solo in VB non in C # (nota: il post originale è taggato C #) , ma dovrebbe comunque dare motivo di mettere in pausa ciò che Microsoft pensa che possano fare con una build di Debug. In effetti, prima di .NET 4.0, il codice VB perde memoria in modo proporzionale al numero di istanze di oggetti con eventi creati a supporto di Modifica e continua. (Anche se questo è stato corretto per https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging , il codice generato sembra brutto, creando WeakReferenceoggetti e aggiungendoli a un elenco statico mentretenendo un lucchetto ) Non voglio certo questo tipo di supporto per il debug in un ambiente di produzione!


Ho rilasciato build di debug molte volte e non ho mai visto un problema. L'unica differenza forse è che la nostra applicazione lato server non è un'app Web che supporta molti utenti. Ma è un'applicazione lato server con un carico di elaborazione molto elevato. Dalla mia esperienza, la differenza tra Debug e Release sembra completamente teorica. Non ho mai visto alcuna differenza pratica con nessuna delle nostre app.
Sam Goldberg,

5

Nella mia esperienza, la cosa peggiore che è uscita dalla modalità di rilascio sono gli oscuri "bug di rilascio". Poiché l'IL (linguaggio intermedio) è ottimizzato in modalità Release, esiste la possibilità di bug che non si sarebbero manifestati in modalità Debug. Esistono altre domande SO relative a questo problema: motivi comuni per i bug nella versione di rilascio non presenti in modalità debug

Questo mi è successo una o due volte in cui una semplice app console funzionava perfettamente in modalità Debug, ma dato lo stesso input esatto, si sarebbe verificato un errore in modalità Release. Questi bug sono ESTREMAMENTE difficili da eseguire il debug (per definizione della modalità di rilascio, ironicamente).


Per seguire, ecco un articolo che fornisce un esempio di bug di rilascio: codeproject.com/KB/trace/ReleaseBug.aspx
Roly,

È comunque un problema se l'applicazione viene testata e approvata con le impostazioni di debug, anche se sopprime gli errori, se ciò causa il fallimento della compilazione della versione durante la distribuzione.
Øyvind Bråthen,

4

Direi che 1) dipende in gran parte dalla tua implementazione. Di solito, la differenza non è così grande. Ho fatto molte misurazioni e spesso non vedevo differenze. Se usi codice non gestito, molti array enormi e cose del genere, la differenza di prestazioni è leggermente maggiore, ma non in un mondo diverso (come in C ++). 2) Solitamente nel codice di rilascio vengono visualizzati meno errori (tolleranza più alta), quindi un interruttore dovrebbe funzionare correttamente.


1
Per il codice associato a IO, una build di rilascio potrebbe non essere più veloce del debug.
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
sembra che in modalità di rilascio a volte i primi elementi di un elenco non siano numerati correttamente. Inoltre, alcuni elementi nell'elenco sono duplicati. :)
Gian Paolo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.