2012-04-16 17 views
14

Sto cercando di ottimizzare la mia applicazione affinché funzioni bene subito dopo l'avvio. Al momento, la sua distribuzione contiene 304 binari (incluse le dipendenze esterne) per un totale di 57 megabyte. È un'applicazione WPF che esegue principalmente l'accesso al database, senza calcoli significativi.In che modo le prestazioni di compilazione .NET JIT (inclusi i metodi dinamici) sono influenzate dalle opzioni di debug dell'immagine del compilatore C#?

Ho scoperto che la configurazione di Debug offre tempi migliori (~ 5 volte di guadagno) per la maggior parte delle operazioni, poiché vengono eseguite per la prima volta durante il ciclo di vita del processo dell'applicazione. Ad esempio, l'apertura di uno specifico schermo all'interno dell'app richiede 0,3 secondi per NGENed Debug, 0,5 secondi per JITted Debug, 1,5 secondi per NGENed Release e 2,5 secondi per JITted Release.

Comprendo che il divario nel tempo di compilazione JIT è causato dal compilatore JIT che applica ottimizzazioni più aggressive per i binari di rilascio. Da quello che posso dire, le configurazioni di Debug e Release differiscono dagli switch /p:DebugType e /p:Optimize passati al compilatore C#, ma vedo lo stesso divario di prestazioni anche se costruisco l'applicazione con /p:Configuration=Release /p:DebugType=full /p:Optimize=false - ovvero, le stesse opzioni di debug dell'immagine come in /p:Configuration=Debug .

Confermo che le opzioni sono state applicate osservando lo DebuggableAttribute applicato all'assieme risultante. Osservando l'uscita NGEN, vedo <debug> aggiunto ai nomi di alcuni assembly che vengono compilati - come distingue NGEN tra gli assembly debug e non-debug? L'operazione in prova utilizza la generazione di codice dinamico: quale livello di ottimizzazione viene applicato al codice dinamico?

Nota: sto utilizzando il framework a 32 bit a causa di dipendenze esterne. Devo aspettarmi risultati diversi su x64?

Nota: anche io non uso la compilazione condizionale. Quindi la fonte compilata è la stessa per entrambe le configurazioni.

+1

Poiché gli assembly Release NGENed sono ancora più lenti di Debug, sei sicuro che JIT sia il problema? Puoi provare un profiler ... Inoltre, controlla di non utilizzare #if DEBUG nel tuo codice. – Guillaume

+1

Stai usando XmlSerializer senza SGEN? http://stackoverflow.com/questions/771727/net-release-build-working-slower-than-debug – Guillaume

+0

Non sto usando '#if DEBUG' (la domanda è stata modificata per riflettere questo). L'applicazione non è necessariamente più lenta in Release - potrebbe anche essere più veloce, ma sto misurando il tempo di avvio a freddo, non il throughput. Sospetto il JITting dei metodi dinamici e quindi chiedo che cosa decide il livello di ottimizzazione di quelli. – cynic

risposta

1

Ok, alcune domande qui.

Da quello che posso dire, Debug e Release configurazioni differiscono dal /p: DEBUGTYPE e/p: Ottimizzare interruttori passati al compilatore C#, ma io vedere lo stesso divario di prestazioni anche se io costruisco l'applicazione con /p: Configuration = Release/p: DebugType = full/p: Optimize = false - che è, le stesse opzioni di debug dell'immagine come in/p: Configuration = Debug.

Anche se le caselle sono gli stessi, cambiando alla modalità di rilascio provoca anche alcuni percorsi di codice interno per essere rimosso, come Debug.Assert() (usato pesantemente nel codice interno di Microsoft). Quindi questi non vengono valutati in fase di esecuzione, il che provoca un miglioramento delle prestazioni. DebugType=full genera un PDB che corrisponde al codice con cui è stato compilato, quindi non è un colpo di performance in sé. Se il PDB viene distribuito, il codice di gestione delle eccezioni utilizzerà il PDB per fornire tracce dello stack più utili rispetto al codice compilato. La modalità di rilascio attiva anche alcuni miglioramenti della memoria, poiché le versioni di debug vengono utilizzate per collegare il debugger.

NGEN è uno strumento utilizzato per "potenzialmente" ottimizzare un'applicazione. Ottimizza il codice per funzionare su specifici del computer in cui ti trovi. Ma può avere degli svantaggi, in quanto il compilatore JIT può apportare modifiche al layout del codice in memoria, mentre NGEN è più statico nella sua natura.

Per le dipendenze a 32 bit (x86), l'app verrà ora eseguita in modalità x86.Se la dipendenza disponesse sia della versione x86 che x64, e se la tua app è compilata in una modalità di compilazione 'Any CPU', il compilatore JIT passerà automaticamente tra il 2. NGEN genererebbe solo una versione specifica per il computer corrente. Quindi se hai fatto NGEN e poi lo hai distribuito, avrebbe funzionato solo per l'architettura specifica che hai compilato.

Se non si utilizzano le funzioni di compilazione condizionale, non importa se si passa da Debug a Release. Ma vedrai un vantaggio in termini di prestazioni in Release.

Con NGEN, suggerisco di testare ampiamente per vedere i vantaggi rispetto al 2. Non sempre si ottengono prestazioni migliori.

+0

Credo che le chiamate ai metodi 'ConditionalAttribute'd vengano eliminate nella fase di compilazione in IL, non in JIT. Indipendentemente da ciò, la differenza nel mio caso non è tra JIT/NGEN ma Debug/Release. – cynic

+0

Quindi stai chiedendo qual è la differenza tra Debug e Release se la configurazione è la stessa? –

+0

Sì. Ci sono dei flag extra in PE (a parte 'DebuggableAttribute')? E, soprattutto, quale livello di ottimizzazione viene applicato al codice generato dinamicamente? – cynic

1

Lo stai eseguendo sotto il debugger ('F5') o senza il debugger ('ctrl + F5')? Se il primo, assicurarsi che Strumenti -> Opzioni -> Debug -> "Sopprimi ottimizzazione JIT sul carico del modulo" è deselezionato

+0

Eseguo le misurazioni eseguendo l'eseguibile da una riga di comando. Quindi non è quello. – cynic

+0

Hrm. Immagino che anche non lo farebbe _semplare_ di DEBUG. –

2

Se, come dici tu, hai 304 assiemi da caricare, allora questa è probabilmente una causa del tuo app in esecuzione lenta. Questo sembra un numero estremamente elevato di assiemi da caricare.

Ogni volta che CLR raggiunge il codice da un altro assembly che non è già caricato nell'AppDomain, deve caricarlo dal disco.

Si potrebbe considerare l'utilizzo di ILMerge per unire alcuni di questi assiemi. Ciò ridurrà il ritardo nel caricamento degli assembly dal disco (si prende uno, più grande, il colpo del disco in avanti).

Potrebbe richiedere qualche sperimentazione, poiché non tutti gli piace essere uniti (in particolare quelli che usano Reflection e dipendono dal nome del file che non cambia mai). Potrebbe anche comportare assemblaggi molto grandi.

+0

Ci proverò, ma penso che sia improbabile la causa della differenza di velocità tra le configurazioni JITted Debug e NGENed Release. Corro i test su una macchina SSD. – cynic