Condividi tramite



Marzo 2016

Volume 31 Numero 3

Il presente articolo è stato tradotto automaticamente.

Compilatori - Profilo gestito - Ottimizzazione guidata con JIT in background

Da Hadi Brais | Marzo 2016

Ottimizzazione delle prestazioni eseguite da un compilatore sono sempre utili. Vale a dire, indipendentemente dal codice che ottiene effettivamente eseguito in fase di esecuzione, l'ottimizzazione migliorerà le prestazioni. Si consideri, ad esempio, spezzo per abilitare la vettorizzazione del ciclo. Le trasformazioni questa ottimizzazione un ciclo in modo che anziché eseguire un'unica operazione nel corpo del ciclo in un singolo set di operandi (ad esempio aggiungere due numeri interi archiviate in array diversi), la stessa operazione che verrà eseguito contemporaneamente su più set di operandi (aggiunta di quattro coppie di numeri interi).

D'altra parte, sono disponibili ottimizzazioni estremamente importante che il compilatore esegue in modo euristico. Ovvero, il compilatore non sa con certezza che tali ottimizzazioni verranno effettivamente funzionano in modo ottimale per il codice che viene eseguito in fase di esecuzione. Le due ottimizzazioni più importanti che rientrano in questa categoria (o probabilmente tra tutte le categorie) sono allocazione del registro e l'inline delle funzioni. Puoi consentire il compilatore prendere decisioni migliori quando si esegue ad esempio eseguendo l'app uno o più volte e fornendo l'input dell'utente tipico mentre allo stesso tempo il codice di registrazione eseguite.

Le informazioni che sono stati raccolti sull'esecuzione dell'applicazione viene definite un profilo. Il compilatore può quindi usare questo profilo per rendere alcune delle relative ottimizzazioni più efficaci, talvolta determinando speedups significativi. Questa tecnica è denominata ottimizzazione PGO (PGO). È necessario utilizzare questa tecnica dopo aver scritto codice leggibile e gestibile, utilizzati algoritmi buona, ottimizzando la località di accesso ai dati, ridotta contesa dei blocchi e attivata su tutte le ottimizzazioni del compilatore possibili, ma non sono ancora soddisfatti delle prestazioni risultante. In generale, PGO consente di migliorare le altre caratteristiche del codice, non solo delle prestazioni. Tuttavia, è possono utilizzare le tecniche descritte in questo articolo per solo migliorare le prestazioni.

Ho discusso dettaglio PGO nativo del compilatore Microsoft Visual C++ in un precedente articolo all'indirizzo msdn.com/magazine/mt422584. Per coloro che leggere l'articolo, ho alcune novità. Usa PGO gestito risulta più semplice. In particolare, la funzionalità verrà illustrato in questo articolo, vale a dire sfondo JIT (detto anche JIT multicore) è molto più semplice. Tuttavia, si tratta di un articolo avanzato. Il team CLR ha scritto un post di blog introduttivo tre anni (bit.ly/1ZnIj9y). Sfondo JIT è supportato in Microsoft .NET Framework 4.5 e versioni successive tutti.

Esistono tre tecniche di ottimizzazione PGO gestite:

  • Compilare il codice gestito al codice binario utilizzando Ngen.exe (processo noto come pre-compilazione JIT) e quindi utilizzare Mpgo.exe per generare i profili che rappresenta i scenari di utilizzo comuni che possono essere utilizzati per ottimizzare le prestazioni del codice binario. È simile a PGO nativo. Si farà riferimento a questa tecnica come statico MPGO.
  • La prima volta che un metodo di linguaggio intermedio (IL) è sui, compilato tramite JIT di generare il codice binario instrumentato che registra le informazioni in fase di esecuzione relative alle parti del metodo di recupero eseguite. Quindi utilizzare successivamente tale profilo in memoria al metodo di compilazione IL re-JIT per generare il codice binario altamente ottimizzato. È inoltre simile a PGO nativa ad eccezione del fatto che tutto ciò che accade in fase di esecuzione. Si farà riferimento a questa tecnica come MPGO dinamica.
  • Utilizzare JIT in background per nascondere quanto più possibile l'overhead a JIT da JIT in modo intelligente la compilazione di metodi di linguaggio intermedio prima vengono effettivamente eseguite per la prima volta. Idealmente, quando che viene chiamato un metodo per la prima volta, avremmo già avuto compilazione JIT e potrebbe non essere necessario attendere che il compilatore JIT compilare il metodo.

È interessante notare che queste tecniche sono state introdotte in .NET Framework 4.5 e versioni successive supportano anche le. MPGO statico funziona solo con immagini native generate da Ngen.exe. Al contrario, MPGO dinamico funziona solo con metodi di linguaggio intermedio. Laddove possibile, utilizzare Ngen.exe per generare immagini native e ottimizzarle utilizzando MPGO statico perché questa tecnica è molto più semplice, mentre nello stesso momento offre speedups soddisfacenti. La terza tecnica, sfondo JIT, è molto diversa dai primi due poiché riduce la compilazione JIT overhead invece di migliorare le prestazioni del codice binario generato e, pertanto, può essere utilizzato insieme a una delle due tecniche. Tuttavia, utilizza background JIT solo in alcuni casi può essere molto utile e migliorare le prestazioni di un particolare scenario di utilizzo comune o di avvio app fino al 50%, che va bene. In questo articolo è incentrato esclusivamente su sfondo JIT. Nella sezione successiva, illustrerò il modo tradizionale di JIT la compilazione di metodi a livello di integrità e l'impatto che le prestazioni. Successivamente, verrà illustrato come sfondo JIT funziona, perché funziona in questo modo e come utilizzarlo in modo corretto.

JIT tradizionale

È probabilmente già un'idea di base del funzionamento del compilatore JIT di .NET poiché sono presenti molti articoli che illustrano questo processo. Tuttavia, si desidera rivedere questo argomento in qualche modo più dettagliato e accuratezza (ma non molto) prima di arrivare a JIT in background in modo che è possibile consultare la sezione successiva e comprensione la funzionalità strettamente.

Si consideri l'esempio illustrato Figura 1. T0 è il thread principale. Le parti del thread verde indicano che il thread è in esecuzione il codice dell'applicazione è in esecuzione alla massima velocità. Si supponga che T0 è in esecuzione in un metodo che è già stato compilato in modalità JIT (la parte verde più in alto) e la successiva istruzione consiste nel chiamare il metodo di linguaggio intermedio M0. Poiché questa è la prima volta M0 verrà eseguita e viene rappresentato in IL, deve essere compilato in codice binario che è possibile eseguire il processore. Per questo motivo, quando viene eseguita l'istruzione di chiamata, viene chiamata una funzione nota come stub del linguaggio intermedio JIT. Infine, questa funzione chiama IL codice di M0 al compilatore JIT di JIT e restituisce l'indirizzo del codice binario generato. Questa operazione non ha nulla a che fare con la stessa app ed è rappresentata da alla parte rossa del T0 per indicare che si tratta di un sovraccarico. Fortunatamente, la posizione di memoria che contiene l'indirizzo dello stub IL JIT verrà corretta con l'indirizzo del codice binario corrispondente in modo che le future chiamate alla stessa funzione eseguita alla massima velocità.

L'Overhead della tradizionale JIT durante l'esecuzione di codice gestito
Figura 1, l'Overhead della tradizionale JIT durante l'esecuzione di codice gestito

A questo punto, dopo l'uscita dal M0, viene eseguito altro codice che è già stato compilato tramite JIT e quindi IL M1 viene chiamato il metodo. Proprio come con M0, viene chiamato lo stub del linguaggio intermedio JIT, che a sua volta chiama il compilatore JIT per compilare il metodo e restituisce l'indirizzo del codice binario. Dopo l'uscita dal M1, viene eseguito una quantità maggiore di codice binario e quindi due ulteriori thread, T1 e T2, avviare l'esecuzione. È interessante.

Dopo l'esecuzione di metodi che sono già stati compilato tramite JIT, T1 e T2 si intende chiamare il metodo di linguaggio intermedio M3, che non è mai stato chiamato prima e, pertanto, deve essere compilato tramite JIT. Internamente, il compilatore JIT gestisce un elenco di tutti i metodi che vengono compilati tramite JIT. È disponibile un elenco per ogni dominio applicazione e uno per il codice condiviso. Questo elenco è protetto da un blocco e tutti gli elementi vengono protetti anche dal proprio blocco in modo che più thread possono eseguire in modo sicuro nella compilazione JIT contemporaneamente. Che cosa accade in questo caso è che un thread, ad esempio T1, sarà JIT il metodo di compilazione e sprecare tempo esegue operazioni che non ha nulla a che fare con l'applicazione anche se T2 è nulla, semplicemente in attesa di un blocco perché in realtà non ha nulla a, fino a quando il codice binario M3 diventa disponibile. Allo stesso tempo, T0 verrà essere compilazione M2. Al termine di un metodo di compilazione JIT, un thread sostituisce l'indirizzo dello stub JIT IL con l'indirizzo del codice binario, rilascia i blocchi ed esegue il metodo. Si noti che T2 verrà infine riattivare e semplicemente eseguire M3.

Il resto del codice che viene eseguito da questi thread è illustrato nelle barre verdi in Figura 1. Ciò significa che l'app sia in esecuzione alla massima velocità. Anche quando un nuovo thread, T3, viene avviata l'esecuzione, tutti i metodi che è necessario eseguire sono già stati compilato tramite JIT e, pertanto, viene eseguita anche alla massima velocità. Le prestazioni risultante diventano molto vicino le prestazioni del codice nativo.

All'incirca a proposito, la durata di ognuno di questi segmenti rosso principalmente dipende la quantità di tempo impiegato per JIT è il metodo, che a sua volta dipende da come grande e complesso il metodo. Possono variare da pochi microsecondi a decine di millisecondi (escluso il tempo per il caricamento di assembly richiesti o moduli). Se l'avvio di un'applicazione richiede l'esecuzione per la prima volta metodi minore di 100, non è un'operazione complessa. Tuttavia, se richiede l'esecuzione per la prima volta centinaia o migliaia di metodi, i segmenti di impatto di tutte risultante rossa potrebbero essere significativi, soprattutto quando il tempo che necessario a JIT un metodo è paragonabile al tempo impiegato per eseguire il metodo, provocando un rallentamento della percentuale di due cifre. Ad esempio, se un'applicazione richiede l'esecuzione delle migliaia diversi metodi all'avvio con un tempo medio di JIT di 3 millisecondi, occorrere 3 secondi per completare l'avvio. Che è un'operazione complessa. Non è valido per l'azienda perché i clienti non sarà soddisfatti.

Si noti che è possibile che più di un thread esegue la compilazione JIT lo stesso metodo nello stesso momento. È inoltre possibile che non riesce al primo tentativo di JIT, ma la seconda ha esito positivo. Infine, è anche possibile che un metodo che è già stato compilato tramite JIT viene compilato tramite JIT re. Tuttavia, tutti questi casi non rientrano nell'ambito di questo articolo e non è necessario essere consapevoli di essi quando si utilizza in background JIT.

Sfondo JIT

Impossibile evitare o notevolmente ridotta JIT compilazione overhead discusso nella sezione precedente. È necessario metodi IL JIT per eseguirle. Operazioni possibili, tuttavia, è modificare l'ora in cui si verifica questo sovraccarico. L'informazione chiave è che anziché in attesa di un metodo da chiamare per la prima volta a JIT, IL, è possibile JIT in precedenza tale metodo in modo che quando che viene chiamato, il codice binario sarebbe già stato generato. Se è ottenuto il risultato desiderato, tutti i thread nel Figura 1sarebbe verde e sarebbe tutte eseguite alla massima velocità, come se sta eseguendo un'immagine nativa NGEN o, forse meglio. Ma prima è stato visualizzato, è necessario risolvere due problemi.

Il primo problema è che se si intende JIT un metodo prima del necessario, il thread su cui si intende JIT? Non è difficile vedere che il modo migliore per risolvere questo problema è che un thread dedicato che viene eseguito in background e metodi JIT che possono essere eseguite più rapidamente possibile. Di conseguenza, funziona solo se sono disponibili almeno due core, che rappresenta quasi sempre il caso, in modo che il compilatore JIT la compilazione di overhead è nascosto dall'esecuzione di codice dell'applicazione sovrapposto.

Il secondo problema è la seguente: Come è possibile sapere quale metodo a JIT successivamente prima che venga chiamato per la prima volta? Tenere presente che in genere sono presenti chiamate al metodo condizionale in ogni metodo e pertanto non è possibile semplicemente JIT tutti i metodi che è possibile che venga chiamati o essere troppo speculative nella scelta quindi i metodi da JIT. È molto probabile che il thread in background JIT cade dietro il thread dell'app molto rapidamente. Si tratta in profili entrano in gioco. Esercizio innanzitutto l'avvio dell'app e qualsiasi scenari di utilizzo comuni e record il cui metodi sono stati compilati tramite JIT e l'ordine in cui sono stati compilati separatamente per ogni scenario JIT. È quindi possibile pubblicare l'app con i profili di registrazione in modo che quando è in esecuzione sul computer dell'utente, il compilatore JIT la compilazione di overhead sarà minimo per quanto riguarda il tempo reale (si tratta di come l'utente percepisce tempo e prestazioni). Questa funzionalità è denominata background JIT ed è possibile utilizzare con il minimo sforzo da parte degli.

Nella sezione precedente, si è visto come il compilatore JIT può metodi diversi di compilazione JIT in parallelo in diversi thread. Tecnicamente, tale JIT tradizionale è già multicore. È poco chiaro che la documentazione MSDN fa riferimento alla funzionalità come JIT multicore, in base ai requisiti di almeno due core, anziché in base la caratteristica distintiva e sfortunato. Si sta utilizzando il nome "sfondo JIT" perché questo è quello che si desidera distribuire. PerfView è incorporato il supporto per questa funzionalità, viene utilizzato lo sfondo di nome JIT. Si noti che il nome "JIT multicore" è il nome utilizzato da Microsoft nelle prime fasi di sviluppo. Nella parte restante di questa sezione, spiegherò tutto che è necessario eseguire per applicare questa tecnica per il codice e le modifiche al modello tradizionale di JIT. Inoltre mostrerò come usare PerfView per misurare il vantaggio di background JIT quando si utilizza questo proprie applicazioni.

Per utilizzare sfondo JIT, è necessario indicare al runtime dove inserire i profili (uno per ogni scenario che attiva un lotto di compilazione JIT). È inoltre necessario indicare al runtime il profilo da utilizzare in modo che assuma il profilo per determinare quali metodi per la compilazione sul thread in background. Naturalmente, questo deve essere eseguita sufficientemente prima dell'avvio dello scenario di utilizzo associato.

Per specificare dove inserire i profili, chiamare il metodo System.Runtime.ProfileOptimization.SetProfileRoot definito in mscorlib. dll. Questo metodo è simile al seguente:

public static void SetProfileRoot(string directoryPath);

Il suo unico parametro, directoryPath, mira a specificare la directory della cartella in cui leggere o scritti tutti i profili. Solo la prima chiamata a questo metodo nello stesso AppDomain ha effetto e altre chiamate vengono ignorate (tuttavia, lo stesso percorso può essere utilizzato da diversi domini applicazioni). Inoltre, se il computer non dispone di almeno due core, qualsiasi chiamata a SetProfileRoot viene ignorato. L'unica cosa che questo metodo non è di archiviare la directory specificata in una variabile interna in modo che può essere utilizzato quando necessario, in un secondo momento. Questo metodo viene chiamato in genere dall'eseguibile (. File EXE) del processo durante l'inizializzazione. Librerie condivise non chiamarlo. È possibile chiamare questo metodo ogni volta che durante l'esecuzione dell'applicazione, ma prima di qualsiasi chiamata al metodo ProfileOptimization.StartProfile. L'altro metodo è simile al seguente:

public static void StartProfile(string profile);

Quando l'applicazione sta per passare attraverso un percorso di esecuzione a cui si desidera ottimizzare (ad esempio avvio) le prestazioni, chiamare questo metodo e passare il nome del file e l'estensione del profilo. Se il file non esiste, un profilo è registrato e archiviato in un file con il nome specificato nella cartella specificata utilizzando SetProfileRoot. Questo processo è denominato "registrazione profilo". Se il file specificato esiste e contiene un profilo JIT sfondo valido, sfondo JIT ha effetto in un background dedicato metodi di compilazione JIT thread, scelti in base alla quale il profilo indicato. Questo processo è denominato "profilo multipli". Durante la riproduzione del profilo, il comportamento anomalo dall'app verrà comunque registrato e lo stesso profilo di input verrà sostituito.

Non è possibile riprodurre un profilo senza registrazione; attualmente non è supportata. È possibile chiamare più volte specificando diversi profili StartProfile adatto per diversi percorsi di esecuzione. Questo metodo non ha alcun effetto se è stato chiamato prima di inizializzare la radice del profilo utilizzando SetProfileRoot. Inoltre, entrambi i metodi non hanno effetto se l'argomento specificato non è valido in alcun modo. In effetti, questi metodi non genera eccezioni o restituiscono codici di errore di non avere effetto sul comportamento delle applicazioni in alcun modo indesiderato. Entrambi sono thread-safe, esattamente come ogni altro metodo statico in framework.

Ad esempio, se si desidera migliorare le prestazioni di avvio, è possibile chiamare questi due metodi come primo passaggio nella funzione main. Se si desidera migliorare le prestazioni di un determinato scenario di utilizzo, chiamare StartProfile quando l'utente deve avviare tale scenario e chiamare SetProfileRoot in qualsiasi momento precedente. Tenere presente che tutto ciò che accade in locale in AppDomain.

Questo è tutto che è necessario eseguire per l'utilizzo di background JIT del codice. È così semplice che è possibile provare appena senza pensare troppo su che sarà utile o non. È quindi possibile misurare l'aumento di velocità ottenuto per determinare se vale la pena di conservazione. Se l'aumento di velocità è almeno il 15%, è necessario mantenerla. In caso contrario, è la chiamata. Ora spiegherò in dettaglio il funzionamento.

Ogni volta che viene chiamato StartProfile, nel contesto del dominio applicazione in cui è attualmente in esecuzione il codice vengono eseguite le seguenti azioni:

  1. Tutto il contenuto del file che contiene il profilo (se presente) viene copiato in memoria. Il file viene quindi chiuso.
  2. Se non è la prima volta che è stato chiamato StartProfile, già sarà disponibile un thread JIT in background in esecuzione. In questo caso, viene terminata e viene creato un nuovo thread di background. Il thread che ha chiamato StartProfile restituisce al chiamante.
  3. Questo passaggio viene eseguito in background thread JIT. Il profilo viene analizzato. I metodi registrati sono nell'ordine in che cui sono stati registrati in modo sequenziale e più velocemente possibile compilato tramite JIT. Questo passaggio costituisce il profilo di esecuzione processo.

È tutto per quanto riguarda il thread in background. Se ha terminato la compilazione di tutti i metodi registrati JIT, verrà terminato in modo invisibile. Se si verifica un errore durante l'analisi o i metodi di compilazione JIT, il thread viene terminato automaticamente. Se un assembly o un modulo che non è stato caricato e viene richiesto a un metodo JIT, non verrà caricata e quindi il metodo non sarà compilato tramite JIT. Sfondo JIT è stato progettato in modo che non modifica il comportamento del programma per quanto possibile. Quando viene caricato un modulo, viene eseguito il relativo costruttore. Inoltre, quando un modulo non viene trovato, vengono chiamati callback registrato con l'evento System.Reflection.Assembly.ModuleResolve. Pertanto, se il thread in background carica un modulo in precedenza superiori in caso contrario, potrebbe cambiare il comportamento di queste funzioni. Si applica in modo analogo ai callback registrato con l'evento System.AppDomain.AssemblyLoad. Poiché lo sfondo JIT non carica i moduli che necessari, potrebbe non essere in grado di compilare molti dei metodi registrati, causando modesto vantaggio.

Non è stato è lecito chiedersi, creare più di un thread in background per JIT altri metodi? Bene, in primo luogo, questi thread sono complesse e pertanto potrebbero competere con i thread dell'app. In secondo luogo, più di questi thread implica ulteriori conflitto di sincronizzazione di thread. In terzo luogo, è probabile che i metodi get compilato tramite JIT ma mai chiamati da qualsiasi thread app. Al contrario, potrebbe essere chiamato un metodo per la prima volta che non viene registrata nel profilo o prima di raggiungere il thread multicore compilato tramite JIT. A causa di questi problemi, con più di un thread in background potrebbero non essere molto utili. Tuttavia, il team CLR può essere utile in futuro (specialmente se la restrizione di caricamento dei moduli può essere ridotte). A questo punto è necessario discutere cosa accade in thread app tra cui il profilo di registrazione di processo.

Figura 2 Mostra lo stesso esempio come in Figura 1 ad eccezione del fatto che in background è abilitato per JIT. Questo è un thread in background M0, M1, M3 e M2, i metodi nell'ordine di compilazione JIT. Si noti come il thread in background è racing contro i thread app T0, T1, T2 e T3. Il thread in background ha JIT ogni metodo prima che venga chiamato per la prima volta da qualsiasi thread per soddisfare lo scopo della vita. La discussione seguente presuppone che questo è il caso con M0, M1 e M3, ma non abbastanza con M2.

Un esempio che mostra l'ottimizzazione JIT in Background rispetto alle figura 1
Figura 2, un esempio che mostra l'ottimizzazione JIT in Background rispetto alle figura 1

Quando sta per chiamare M0 T0, background thread JIT è già stato compilato tramite JIT. Tuttavia, l'indirizzo del metodo non è stata ancora e patch ancora punti allo stub IL JIT. Background thread JIT potrebbe aver corretto, ma non per determinare in un secondo momento se è stato chiamato il metodo o non. Queste informazioni viene utilizzate dal team di CLR per valutare background JIT. Pertanto, viene chiamato lo stub del linguaggio intermedio JIT e rileva che il metodo è già stato compilato sul thread in background. L'unica cosa è necessario eseguire è l'indirizzo patch ed eseguire il metodo. Si noti come il sovraccarico di compila tramite JIT è stata completamente eliminato sul thread corrente. M1 riceve lo stesso trattamento quando viene chiamato su T0. M3 riceve lo stesso trattamento, anche quando viene chiamato su T1. Tuttavia, quando chiama il metodo M3 T2 (fare riferimento a Figura 1), l'indirizzo del metodo è stato aggiornato da T1 molto rapidamente e in tal caso chiama direttamente il codice binario effettivo del metodo. T0 chiama quindi M2. Tuttavia, background thread JIT non è stata completata ma la compilazione del metodo e, pertanto, T0 JIT è in attesa sul blocco JIT del metodo. Quando il metodo viene compilato tramite JIT, T0 riattivato e viene chiamato.

Non ancora trattato come metodi vengano registrati nel profilo. È inoltre possibile che il thread di un'applicazione chiama un metodo che background thread JIT non è ancora iniziato a compilazione JIT (o non verrà compilato tramite JIT, perché non è presente nel profilo). Che ho compilato personalmente i passaggi eseguiti su un thread dell'applicazione durante la chiamata a un valore statico o un metodo dinamico di linguaggio intermedio che non è stato che compilazione JIT ancora nell'algoritmo seguente:

  1. Acquisire il blocco di elenco JIT del dominio applicazione in cui è presente il metodo.
  2. Se il codice binario è già stato generato da un altro thread app, rilasciare il blocco di elenco JIT e andare al passaggio 13.
  3. Aggiungere un nuovo elemento all'elenco che rappresenta il processo di lavoro JIT del metodo se non esiste. Se esiste già, il conteggio dei riferimenti viene incrementato.
  4. Rilasciare il blocco di elenco JIT.
  5. Acquisire il blocco JIT del metodo.
  6. Se il codice binario è già stato generato da un altro thread app, andare al passaggio 11.
  7. Se il metodo non è supportato in background JIT, ignorare questo passaggio. Attualmente, sfondo che JIT supporta solo in modo statico generato metodi IL definiti nell'assembly che non sono stati caricati con Load. Se il metodo è supportato, controllare se è già stato compilato dallo sfondo thread JIT JIT. In questo caso, il metodo di registrazione e andare al passaggio 9. In caso contrario, andare al passaggio successivo.
  8. Il metodo JIT. Il compilatore JIT esamina il linguaggio intermedio del metodo, tutti i tipi necessari determina, garantisce che tutti gli assembly vengono caricati e necessari vengono creati tutti gli oggetti di tipo richiesto. Se si verifica un errore, viene generata un'eccezione. Questa operazione comporta la maggior parte dell'overhead.
  9. Sostituire l'indirizzo dello stub JIT IL con l'indirizzo del codice binario effettivo del metodo.
  10. Se il metodo è stato compilato da un thread dell'applicazione piuttosto che per lo sfondo thread JIT, è un registratore JIT in background attivo e il metodo è supportato dalla modalità in background JIT; JIT il metodo viene registrato in un profilo in memoria. L'ordine in cui i metodi sono stati compilati tramite JIT viene mantenuto nel profilo. Si noti che il codice binario generato non viene registrato.
  11. Rilasciare il metodo lock JIT.
  12. Ridurre in modo sicuro il conteggio dei riferimenti del metodo utilizzando il blocco di elenco. Se diventa zero, l'elemento viene rimosso.
  13. Eseguire il metodo.

Il processo di registrazione JIT in background viene terminata quando si verifica una delle situazioni seguenti:

  • Per qualsiasi motivo viene scaricato AppDomain associato al gestore JIT in background.
  • StartProfile viene chiamata nuovamente nello stesso dominio dell'applicazione.
  • La velocità con cui i metodi sono JIT compilati nel thread dell'app diventa molto piccola. Ciò indica che l'applicazione ha raggiunto uno stato stabile dove raramente richiede la compilazione JIT. Metodi che ottengono JIT compilate dopo questo punto non si è interessati allo sfondo JIT.
  • Uno dei limiti di registrazione è stato raggiunto. Il numero massimo di moduli è 512, il numero massimo di metodi è 16.384 e la durata più lunga continua di registrazione è un minuto.

Al termine del processo di registrazione, il profilo in memoria registrato viene archiviato nel file specificato. In questo modo, alla successiva esecuzione dell'applicazione, preleva il profilo che rifletta il comportamento anomalo dall'app durante l'ultima esecuzione. Come ho accennato prima, i profili vengono sempre sovrascritte. Se si desidera mantenere il profilo corrente, è necessario apportare manualmente una copia prima di chiamare StartProfile. Le dimensioni di un profilo in genere non superano alcuni decine di kilobyte.

Prima della chiusura di questa sezione, vorrei parlare selezionando le radici di profilo. Per le applicazioni client, è possibile specificare una directory specifiche dell'utente o un relativo dell'applicazione, a seconda se si desidera disporre di diversi set di profili per diversi utenti o solo un set di profili per tutti gli utenti. Per le applicazioni ASP.NET e Silverlight, sarà probabilmente essere usare un'istanza di directory relativo dell'applicazione. Infatti, a partire da ASP.NET 4.5 e 4.5 di Silverlight, vengono archiviati in background JIT è abilitato per impostazione predefinita e i profili accanto all'app. Il runtime si comporterà come se è stato chiamato SetProfileRoot e StartProfile nel metodo main e quindi non è necessario eseguire alcuna operazione per utilizzare la funzionalità. È comunque possibile chiamare StartProfile, tuttavia, come descritto in precedenza. È possibile disattivare automatico in background JIT impostando il flag profileGuidedOptimizations su None nel file di configurazione Web, come descritto nel post di Blog di .NET, "Un semplice soluzione per migliorare App avviare delle prestazioni" (bit.ly/1ZnIj9y). Questo flag può richiedere solo un altro valore, vale a dire tutti, che consente di background JIT (impostazione predefinita).

Sfondo JIT in azione

Sfondo JIT è un provider di traccia eventi per Windows (ETW). Vale a dire, fornisce un numero di eventi correlati alla funzionalità agli utenti ETW, ad esempio la registrazione delle prestazioni di Windows e PerfView. Questi eventi consentono di diagnosticare eventuali inefficienze o errori che si sono verificati in background JIT. In particolare, è possibile determinare il numero di metodi compilati nel thread in background e il tempo JIT totale di questi metodi. È possibile scaricare PerfView da bit.ly/1PpJUpv (non necessaria, l'installazione solo decomprimere ed eseguire). Per dimostrazione utilizzerò il semplice codice seguente:

class Program {
  const int OneSecond = 1000;
  static void PrintHelloWorld() {
    Console.WriteLine("Hello, World!");
  }
  static void Main() {
    ProfileOptimization.SetProfileRoot(@"C:\Users\Hadi\Desktop");
    ProfileOptimization.StartProfile("HelloWorld Profile");
    Thread.Sleep(OneSecond);
    PrintHelloWorld();
  }
}

Nella funzione main, SetProfileRoot e StartProfile vengono chiamati per configurare background JIT. Il thread viene inserito di sospensione per circa un secondo e quindi viene chiamato un metodo, PrintHelloWorld. Questo metodo chiama console. WriteLine semplicemente e restituisce. Compilare il codice di un file eseguibile di linguaggio intermedio. Si noti che Console.WriteLined non richiede la compilazione JIT perché è già stato compilato utilizzando NGEN durante l'installazione di .NET Framework nel computer in uso.

Usare PerfView per avviare e analizzare il file eseguibile (per ulteriori informazioni su come eseguire questa operazione, consultare il post di Blog di .NET, "Miglioramento delle prestazioni dell'applicazione con PerfView," in bit.ly/1nabIYC, o il canale 9 PerfView esercitazione bit.ly/23fwp6r). Ricordarsi di selezionare la casella di controllo JIT in Background (richiesto solo in .NET Framework 4.5 e 4.5.1) per abilitare gli eventi di acquisizioni da questa funzionalità. Attendere fino al completamento e quindi aprire la pagina JITStats di PerfView (vedere Figura 3); PerfView indicherà che il processo non utilizza la compilazione JIT in background. Ciò avviene perché la prima esecuzione, un profilo deve essere generato.

Il percorso di JITStats in PerfView
Figura 3. il percorso di JITStats in PerfView

Quindi, ora che è stato generato uno sfondo profilo JIT, usare PerfView per avviare e analizzare il file eseguibile. Questa volta, tuttavia, quando si apre la pagina JITStats, si noterà che un metodo, vale a dire PrintHelloWorld, è stato compilato sul thread JIT in background tramite JIT e non era un metodo, vale a dire Main. Inoltre dirti che si è verificato il 92% del tempo JIT per la compilazione di tutti i metodi di linguaggio intermedio nel thread dell'app. Il report PerfView visualizzerà un elenco di tutti i metodi che sono stati compilati, JIT dal linguaggio intermedio e binari dimensioni di ciascun metodo, che ha compilato tramite JIT il metodo e altre informazioni. È possibile accedere facilmente il set completo di informazioni sugli eventi JIT in background. Tuttavia, a causa della mancanza di spazio, non verrà esaminato nei dettagli.

È lecito chiedersi sullo scopo della sospensione per circa un secondo. Ciò è necessario disporre di PrintHelloWorld sul thread in background compilato tramite JIT. In caso contrario, è probabile che il thread dell'applicazione verrà avviata la compilazione il metodo prima del thread in background. In altre parole, è necessario chiamare StartProfile in anticipo in modo che il thread in background può stare al passo la maggior parte dei casi.

Avvolgendo

Sfondo JIT è un'ottimizzazione PGO è supportata in .NET Framework 4.5 e versioni successive. In questo articolo sono state descritte quasi tutto ciò che occorre conoscere questa funzionalità. Ho dimostrato perché è necessaria questa ottimizzazione, come funziona e come utilizzare correttamente nel codice in dettaglio. Utilizzare questa funzionalità quando NGEN non è pratico o possibile. Poiché è facile da usare, è possibile provare appena, senza pensare troppa sulle se ne trarranno vantaggio app o non. Se si è soddisfatti con l'aumento di velocità ottenuto, mantenerla. In caso contrario, è possibile rimuovere facilmente il. Microsoft ha utilizzato background JIT per migliorare le prestazioni di avvio di alcune delle applicazioni. Mi auguro che utilizzare al meglio nelle applicazioni, nonché, di avvio aumentano di scenari di utilizzo esteso con JIT e avvio dell'applicazione.


Hadi Braisè studioso un dottorato nell'etnia Institute di tecnologia Delhi, cercando le ottimizzazioni del compilatore per la tecnologia di memoria di ultima generazione. Dedica gran parte del suo tempo la scrittura di codice in C / C + + c# e dare approfondita del computer, il framework del compilatore e runtime architetture. Partecipa al blog hadibrais.wordpress.com. Contattarlo all'indirizzo hadi.b@live.com.

Grazie all'esperto tecnico Microsoft seguente per la revisione di questo articolo: Vance Morrison