Condividi tramite


Esercitazione: Risolvere i problemi di inlining delle funzioni in fase di compilazione

Usare la visualizzazione Funzioni di Build Insights per risolvere l'impatto dell'inlining della funzione sul tempo di compilazione nei progetti C++.

Prerequisiti

  • Visual Studio 2022 17.8 o versione successiva.
  • Le informazioni dettagliate sulla compilazione C++ sono abilitate per impostazione predefinita se si installa il carico di lavoro Sviluppo di applicazioni desktop con C++ o il carico di lavoro Sviluppo di giochi con C++.

Screenshot del Programma di installazione di Visual Studio con il carico di lavoro Sviluppo di applicazioni desktop con C++ selezionato.

Viene visualizzato l'elenco dei componenti installati. Build Insights C++ è evidenziato ed è selezionato il che significa che è installato.

Screenshot del Programma di installazione di Visual Studio con il carico di lavoro Sviluppo di giochi con C++ selezionato.

Viene visualizzato l'elenco dei componenti installati. Build Insights C++ è evidenziato ed è selezionato il che significa che è installato.

Panoramica

Compilare Insights, ora integrato in Visual Studio, consente di ottimizzare i tempi di compilazione, in particolare per progetti di grandi dimensioni, ad esempio giochi AAA. Build Insights offre analisi come la visualizzazione Funzioni , che consente di diagnosticare la generazione di codice costosa durante il tempo di compilazione. Visualizza il tempo necessario per generare il codice per ogni funzione e mostra l'impatto di __forceinline.

La __forceinline direttiva indica al compilatore di inline una funzione indipendentemente dalle dimensioni o dalla complessità. L'inlining di una funzione può migliorare le prestazioni di runtime riducendo il sovraccarico della chiamata alla funzione. Il compromesso è che può aumentare le dimensioni del file binario e influire sui tempi di compilazione.

Per le compilazioni ottimizzate, il tempo impiegato per la generazione del codice contribuisce in modo significativo al tempo di compilazione totale. In generale, l'ottimizzazione delle funzioni C++ viene eseguita rapidamente. In casi eccezionali, alcune funzioni possono diventare sufficientemente grandi e complesse da mettere pressione sull'ottimizzatore e rallentare notevolmente le compilazioni.

Questo articolo illustra come usare la visualizzazione Funzioni di Build Insights per trovare colli di bottiglia inlining nella compilazione.

Impostazione delle opzioni di compilazione

Per misurare i risultati di , usare una build di rilascio perché le compilazioni di debug non sono inline __forceinline perché le compilazioni di debug usano l'opzione del __forceinline/Ob0 compilatore, che disabilita tale ottimizzazione. Impostare la build per Release e x64:

  1. Nell'elenco a discesa Configurazioni soluzione scegliere Rilascio.
  2. Nell'elenco a discesa Piattaforme soluzioni scegliere x64.

Screenshot dell'elenco a discesa Configurazione soluzione impostato su Rilascio e l'elenco a discesa Piattaforma soluzione impostato su x64.

Impostare il livello di ottimizzazione su ottimizzazioni massime:

  1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul nome del progetto e selezionare Proprietà.

  2. Nelle proprietà del progetto passare a C/C++Optimization( Ottimizzazione C/C++>).

  3. Impostare l'elenco a discesa Ottimizzazione su Ottimizzazione massima (Favor Speed) (/O2).

    Screenshot della finestra di dialogo delle pagine delle proprietà del progetto. Le impostazioni sono aperte all'Ottimizzazione C/C++ > delle > proprietà di configurazione. L'elenco a discesa Ottimizzazione è impostato su Ottimizzazione massima (Favor Speed) (/O2).

  4. Fare clic su OK per chiudere la finestra di dialogo.

Eseguire Build Insights

In un progetto scelto e usando le opzioni di compilazione versione impostate nella sezione precedente, eseguire Build Insights scegliendo dal menu principale Build Run Build Insights on Selection Rebuild (Compila>informazioni dettagliate compilazione in Ricompila selezione).> È anche possibile fare clic con il pulsante destro del mouse su un progetto in Esplora soluzioni e scegliere Esegui ricompilazione di Build Insights>. Scegliere Ricompila invece di Compila per misurare il tempo di compilazione per l'intero progetto e non solo per i pochi file potrebbero essere sporchi al momento.

Screenshot del menu principale con Run Build Insights on Selection > Rebuild selezionata.

Al termine della compilazione, viene aperto un file ETL (Event Trace Log). Viene salvato nella cartella a cui punta la variabile di ambiente Windows TEMP . Il nome generato si basa sull'ora di raccolta.

Visualizzazione funzione

Nella finestra del file ETL scegliere la scheda Funzioni . Mostra le funzioni compilate e il tempo necessario per generare il codice per ogni funzione. Se la quantità di codice generata per una funzione è trascurabile, non verrà visualizzata nell'elenco per evitare di ridurre le prestazioni della raccolta di eventi di compilazione.

Screenshot del file di visualizzazione funzioni di Build Insights.

Nella colonna Nome funzione eseguirePhysicsCalculations() è evidenziato e contrassegnato con un'icona fire.:::

La colonna Tempo [sec, %] mostra quanto tempo è necessario per compilare ogni funzione nel tempo di responsabilità dell'orologio a parete (WCTR).The Time [sec, %] column show how long it took to compile each function in wall clock responsibility time (WCTR). Questa metrica distribuisce l'ora del clock in base alle funzioni in base all'uso di thread del compilatore paralleli. Ad esempio, se due thread diversi compilano due funzioni diverse contemporaneamente entro un secondo periodo, il WCTR di ogni funzione viene registrato come 0,5 secondi. Ciò riflette la quota proporzionale di ogni funzione del tempo di compilazione totale, tenendo conto delle risorse utilizzate durante l'esecuzione parallela. Pertanto, WCTR fornisce una misura migliore dell'impatto che ogni funzione ha sul tempo di compilazione complessivo negli ambienti in cui si verificano più attività di compilazione contemporaneamente.

La colonna Forceinline Size mostra approssimativamente il numero di istruzioni generate per la funzione. Fare clic sulla freccia di espansione prima del nome della funzione per visualizzare le singole funzioni inlined espanse in tale funzione il numero approssimativo di istruzioni generate per ognuna.

È possibile ordinare l'elenco facendo clic sulla colonna Ora per visualizzare le funzioni che richiedono più tempo per la compilazione. Un'icona "fire" indica che il costo della generazione di tale funzione è elevato e vale la pena analizzare. Un uso eccessivo delle __forceinline funzioni può rallentare notevolmente la compilazione.

È possibile cercare una funzione specifica usando la casella Funzioni filtro . Se il tempo di generazione del codice di una funzione è troppo piccolo, non viene visualizzato nella visualizzazione Funzioni .

Migliorare il tempo di compilazione regolando l'inlining delle funzioni

In questo esempio la performPhysicsCalculations funzione impiega più tempo per la compilazione.

Screenshot della visualizzazione Funzioni di Build Insights.

Nella colonna Nome funzione, performPhysicsCalculations() è evidenziato e contrassegnato con un'icona di fuoco.

Esaminando ulteriormente, selezionando la freccia di espansione prima di tale funzione e quindi ordinando la colonna Forceinline Size dal più alto al più basso, vengono visualizzati i maggiori collaboratori al problema.

Screenshot della visualizzazione Funzioni di Build Insights con una funzione espansa.

performPhysicsCalculations() viene espanso e mostra un lungo elenco di funzioni inlinedi al suo interno. Sono disponibili più istanze di funzioni, ad esempio complexOperation(), recursiveHelper() e sin(). La colonna Forceinline Size mostra che complexOperation() è la funzione inlined più grande a 315 istruzioni. recursiveHelper() include 119 istruzioni. Sin() include 75 istruzioni, ma ne esistono molte più istanze rispetto alle altre funzioni.

Esistono alcune funzioni inlined più grandi, ad esempio Vector2D<float>::complexOperation() e Vector2D<float>::recursiveHelper() che contribuiscono al problema. Esistono tuttavia molte altre istanze (non tutte illustrate qui) di Vector2d<float>::sin(float), Vector2d<float>::cos(float), Vector2D<float>::power(float,int)e Vector2D<float>::factorial(int). Quando si aggiungono tali istruzioni, il numero totale di istruzioni generate supera rapidamente le poche funzioni generate più grandi.

Esaminando queste funzioni nel codice sorgente, si noterà che il tempo di esecuzione verrà speso all'interno dei cicli. Ad esempio, ecco il codice per factorial():

static __forceinline T factorial(int n)
{
    T result = 1;
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j < i; ++j) {
            result *= (i - j) / (T)(j + 1);
        }
    }
    return result;
}

Forse il costo complessivo della chiamata a questa funzione è insignificante rispetto al costo della funzione stessa. Rendere inline una funzione è più utile quando il tempo necessario per chiamare la funzione (push di argomenti nello stack, passaggio alla funzione, estrazione di argomenti restituiti e restituzione dalla funzione) è approssimativamente simile al tempo necessario per eseguire la funzione e quando la funzione viene chiamata molto. Quando questo non è il caso, potrebbe esserci un ritorno in diminuzione per renderlo inline. È possibile provare a rimuovere la __forceinline direttiva da essa per verificare se consente il tempo di compilazione. Il codice per powersin() e cos() è simile in quanto il codice è costituito da un ciclo che verrà eseguito più volte. È possibile provare anche a rimuovere la __forceinline direttiva da tali funzioni.

Si eseguirà di nuovo Build Insights dal menu principale scegliendo Compila>compila informazioni dettagliate sulla ricompilazione selezione.> È anche possibile fare clic con il pulsante destro del mouse su un progetto in Esplora soluzioni e scegliere Esegui ricompilazione di Build Insights>. Si sceglie Ricompila invece di Compila per misurare il tempo di compilazione per l'intero progetto, come in precedenza, e non solo per i pochi file potrebbero essere sporchi al momento.

Il tempo di compilazione va da 25,181 secondi a 13,376 secondi e la performPhysicsCalculations funzione non viene più visualizzata nella visualizzazione Funzioni perché non contribuisce abbastanza al tempo di compilazione da contare.

Screenshot del file di intestazione del vettore 2D.

Nella colonna Nome funzione eseguirePhysicsCalculations() è evidenziato e contrassegnato con un'icona fire.:::

Il tempo della sessione di diagnostica è il tempo complessivo impiegato per la compilazione e qualsiasi overhead per raccogliere i dati di Build Insights.

Il passaggio successivo consiste nel profilare l'applicazione per verificare se le prestazioni dell'applicazione sono influenzate negativamente dalla modifica. In caso affermativo, è possibile aggiungere __forceinline di nuovo in modo selettivo in base alle esigenze.

Fare doppio clic, fare clic con il pulsante destro del mouse o premere INVIO in un file nella visualizzazione Funzioni per aprire il codice sorgente per tale file.

Screenshot di un clic con il pulsante destro del mouse su un file nella visualizzazione Funzioni. L'opzione di menu Vai al file di origine è evidenziata.

Suggerimenti

  • È possibile >salvare il file con nome ETL in un percorso più permanente per mantenere un record del tempo di compilazione. È quindi possibile confrontarlo con le build future per verificare se le modifiche migliorano il tempo di compilazione.
  • Se si chiude inavvertitamente la finestra Build Insights, riaprirla individuando il <dateandtime>.etl file nella cartella temporanea. La TEMP variabile di ambiente Windows fornisce il percorso della cartella dei file temporanei.
  • Per esaminare i dati di Build Insights con Windows analizzatore prestazioni (WPA), fare clic sul pulsante Apri in WPA nella parte inferiore destra della finestra ETL.
  • Trascinare le colonne per modificare l'ordine delle colonne. Ad esempio, è consigliabile spostare la colonna Ora come prima colonna. È possibile nascondere le colonne facendo clic con il pulsante destro del mouse sull'intestazione di colonna e deselezionando le colonne che non si desidera visualizzare.
  • La visualizzazione Funzioni fornisce una casella di filtro per trovare una funzione a cui si è interessati. Corrisponde parzialmente al nome specificato.
  • Se si dimentica come interpretare la visualizzazione Funzioni che sta tentando di visualizzare, passare il puntatore del mouse sulla scheda per visualizzare una descrizione comando che descrive la visualizzazione. Se si passa il puntatore del mouse sulla scheda Funzioni , la descrizione comando indica: "Visualizza che mostra le statistiche per le funzioni in cui i nodi figlio sono funzioni forzate".

Risoluzione dei problemi

  • Se la finestra Build Insights non viene visualizzata, eseguire una ricompilazione anziché una compilazione. La finestra Build Insights non viene visualizzata se non viene effettivamente compilata alcuna compilazione; che può essere il caso se nessun file è stato modificato dopo l'ultima compilazione.
  • Se la visualizzazione Funzioni non mostra alcuna funzione, è possibile che non si stia creando con le impostazioni di ottimizzazione corrette. Assicurarsi di compilare release con ottimizzazioni complete, come descritto in Impostare le opzioni di compilazione. Inoltre, se il tempo di generazione del codice di una funzione è troppo piccolo, non viene visualizzato nell'elenco.

Vedi anche

Funzioni inline (C++)
Compilazioni C++ più veloci, semplificate: una nuova metrica per il tempo
Video di Build Insights in Visual Studio - Pure Virtual C++ 2023
Risolvere i problemi relativi all'impatto del file di intestazione sul tempo di compilazione
Visualizzazione funzioni per Build Insights in Visual Studio 2022 17.8
Esercitazione: vcperf e Windows analizzatore prestazioni
Miglioramento del tempo di generazione del codice con C++ Build Insights