Usa l'ottimizzazione guidata da profili di esempio (SPGO) per migliorare le prestazioni di C++

L'ottimizzazione guidata dal profilo (PGO) usa i dati di esecuzione per aiutare il compilatore a prendere decisioni di ottimizzazione migliori. Utilizzando i dati del profilo di esecuzione raccolti da carichi di lavoro rappresentativi, PGO consente al compilatore di adottare decisioni più efficaci in materia di inlining, layout del codice e separazione tra il codice eseguito più frequentemente e quello eseguito meno frequentemente. Queste decisioni sono impossibili da prendere solo dall'analisi statica.

SPGO adotta un approccio diverso. Anziché instrumentare il file binario ed eseguirlo tramite scenari di training sintetici, SPGO usa il campionamento dei contatori delle prestazioni hardware raccolti dai file binari di rilascio effettivi. I processori moderni offrono funzionalità di campionamento hardware. È possibile raccogliere questi esempi con un sovraccarico di runtime trascurabile, semplificando la raccolta dei profili di runtime direttamente dal codice di produzione.

Poiché i profili SPGO rilasciano bit anziché build instrumentate, consente una maggiore flessibilità in dove e come si raccolgono i dati. È possibile raccogliere profili di runtime da server di produzione, computer per sviluppatori, lab per le prestazioni o qualsiasi combinazione. Il risultato è un binario che esegue i percorsi critici in modo più efficiente, con un incremento delle prestazioni tipicamente del 5-15%, a seconda della qualità dei dati di profilazione.

In questa esercitazione viene illustrato il flusso di lavoro SPGO completo: creare un'app di esempio, profilarla usando xperf, preparare i dati del profilo e ricompilare con i dati del profilo. Al termine, è possibile applicare lo stesso processo ai propri progetti.

Prerequisiti

Prima di iniziare, assicurarsi di avere il software e l'hardware seguenti.

Software

  • MSVC build tools for x64/x86/ARM64 v14.51 or later: installarli tramite il programma di installazione di Visual Studio. In Singoli componenti cercare "Strumenti di compilazione MSVC".
  • Windows Performance Toolkit (xperf.exe)—Il profiler xperf raccoglie i dati di campionamento durante l'esecuzione del programma. Scarica il Windows Assessment and Deployment Kit (ADK) da ADK install. Quando si esegue il programma di installazione di ADK, selezionare il componente Windows Performance Toolkit per ottenere xperf. Non è necessario installare l'ADK completo.
  • File di testo War and Peace: usato come carico di lavoro di esempio per generare dati di profilatura. Scaricarlo da Project Gutenberg: https://www.gutenberg.org/ebooks/2600. Salvarlo come file di testo normale nella directory di lavoro.

Requisiti hardware

Il tutorial prevede tre percorsi di profilazione. Il percorso usato dipende dall'hardware. Eseguire i comandi di rilevamento in Scegliere il metodo di profilatura per individuare il percorso supportato dal computer. Per il momento, usare questa tabella per verificare di soddisfare almeno uno dei requisiti.

Path Requisito della CPU Note
LBR (risultati migliori) Last Branch Records (LBR) sono contatori delle prestazioni forniti nelle CPU Intel Haswell (4a generazione Core, 2013) o versioni successive; AMD Zen 4 (2022) o versione successiva, ARM64 ARMv9.2-A (2020) o versione successiva Fornisce i dati di ramo migliori. Per altre informazioni su LBR, vedere Introduzione ai record dell'ultimo ramo
Modalità PMC/IP (buoni risultati) I contatori di Monitoraggio delle prestazioni (PMC) sono supportati in qualsiasi CPU x64 con un'unità di monitoraggio delle prestazioni (PMU) Funziona sulla maggior parte delle CPU moderne in cui LBR non è disponibile. Per altre informazioni su PMC, vedere Registrazione di eventi di prestazioni hardware (PMU) ed eventi di registrazione delle prestazioni hardware (PMU) con esempi completi
Timer del sistema operativo (funziona ovunque) Qualsiasi CPU x64 o ARM64, incluse le macchine virtuali e le macchine virtuali Azure Esempi di bassa fedeltà, ma sempre disponibili

La maggior parte degli sviluppatori dispone di hardware desktop x64 moderno con supporto LBR. Le macchine virtuali e alcuni hardware meno recenti hanno PMC o un timer del sistema operativo.

Funzionamento di SPGO

SPGO raccoglie i dati del profilo dal file binario in esecuzione e li invia al compilatore al momento della compilazione successiva. Il compilatore usa tali dati per prendere decisioni migliori sull'inlining, il layout del codice e la stima dei rami. Una comodità è che non è necessaria alcuna strumentazione.

Il flusso di lavoro è:

  1. Crea il binario usando il flag del linker /spgo. Questo passaggio crea un database di profilo di esempio vuoto (.spd file).
  2. Profilare il file binario usando xperf per produrre un file di traccia ETL.
  3. Convertire ETL in un file SPT usando SPTAggregate.exe, quindi convertire SPT in un file SPD usando SPDConvert.exe.
  4. Ricompilare con il flag del linker /spdin che punta al database del profilo di esempio (SPD) popolato. Il linker applica le ottimizzazioni SPGO.

L'utilità di ottimizzazione usa il spd per rispondere a domande come: quali rami vengono usati più spesso? Quali funzioni vengono chiamate nei cicli critici? Questo processo produce un layout del codice migliore e decisioni di inlining più efficaci rispetto alla sola analisi statica.

SPGO funziona sia con C che con C++. Il flusso di lavoro e i flag sono identici per entrambe le lingue.

Casi ideali per SPGO: Applicazioni C/C++ di grandi dimensioni, ricche di rami, con loop interni serrati. Aumenta la scalabilità con le dimensioni della codebase e la complessità dei rami. Il piccolo esempio di questa esercitazione mostra circa 7% miglioramento. Le codebase di produzione più grandi spesso riscontrano un miglioramento maggiore.

Confronto tra processi di compilazione

Questa sezione spiega come SPGO si inserisce nella pipeline di compilazione, se vuoi capirne il funzionamento.

Processo di compilazione normale

In una build di versione C/C++ standard:

  • Ingressi: File di codice sorgente (.cpp, .h) e flag del compilatore in modalità di rilascio (/O2, /GLe così via).
  • Procedura: Il compilatore applica ottimizzazioni standard, come le euristiche di inlining, le ipotesi di previsione dei salti e le decisioni sulla disposizione del codice, basandosi esclusivamente sull'analisi statica. Non dispone di dati sul comportamento effettivo del programma durante l'esecuzione.
  • Output: Eseguibile (.exe), file DLL (.dll), informazioni di debug (.pdb).

Diagramma del normale processo di compilazione della build di rilascio che mostra i file di codice sorgente e l'opzione di esempio del compilatore /GL come input per una fase di compilazione, che produce file .exe, .dll e .pdb come output.

Senza dati di runtime, percorsi caldi e percorsi freddi ricevono un trattamento simile.

Processo di compilazione con SPGO abilitato

SPGO aggiunge i dati di profilatura come nuovo input alla pipeline di compilazione:

  • Input: codice sorgente, il file .spd di profilo (conteggi dei campioni da un'esecuzione di profilazione), i flag del compilatore in modalità release, /link /spgo e /spdin:<path> per specificare un file SPD di input (se non specificato, per impostazione predefinita viene usato un file .spd con il nome del file binario, situato nella cartella obj).
  • Processo: Il linker legge l'SPD insieme al codice intermedio. Usa i dati sulla frequenza dei rami per prendere decisioni migliori per l'inlining, il layout del codice e l'ordinamento dei rami. Le funzioni usate più di frequente vengono disposte per un accesso rapido; il codice usato meno spesso viene spostato fuori dal percorso critico.
  • Output: File eseguibili ottimizzati (.exe), file DLL ottimizzati (.dll), informazioni di debug (.pdb) e un nuovo .spd file per iterazioni di profilatura future.

Diagramma del processo di compilazione abilitato per SPGO che mostra i file di dati del codice sorgente e del profilo (spd) come input per il passaggio di compilazione con un'opzione del linker aggiuntiva /spgo. Il processo di compilazione restituisce .exeottimizzati, .dll, informazioni di debug (con estensione pdb) e nuovi file di dati del profilo (con estensione spd).

L’intuizione chiave: SPGO sposta le decisioni di ottimizzazione dalle euristiche del compilatore e del linker a scelte guidate dai dati basate sull’esecuzione reale.

Flag di chiave

Flag Type Scopo
/spgo Linker Abilita SPGO. Incorpora i metadati SPGO nel file binario e crea un file di output vuoto .spd , a meno che non /spdin venga specificato, nel qual caso il file specificato .spd viene usato come input.
/spdin:<path> Linker SPD di input: fornisce i dati del profilo al linker per l'ottimizzazione
/spd:<path> Linker Percorso SPD di output: specifica dove viene scritto il nuovo SPD (facoltativo; il valore predefinito è la stessa directory del file binario). Funge da percorso SPD di input se /spdin non è specificato.
/GL Compilatore Ottimizzazione dell'intero programma necessaria per il funzionamento di SPGO tra unità di traduzione
/O1, /O2 (Riduci al minimo le dimensioni, massimizza la velocità) Compilatore Ottimizza per la velocità; abilita ottimizzazioni aggressive che SPGO può migliorare

Differenze tra SPGO e PGO

PGO (ottimizzazione guidata dal profilo) richiede di compilare il binario con flag di strumentazione (/GENPROFILE), eseguire il binario strumentato, più lento, per raccogliere i file di conteggio dell’esecuzione .pgc, quindi rieseguire il linking con /USEPROFILE. Il compilatore ottiene i conteggi di esecuzione esatti, ma è necessario instrumentare prima il codice. Per altre informazioni su questo processo, vedere Ottimizzazioni guidate dal profilo.

SPGO usa i contatori hardware delle prestazioni della CPU per raccogliere campioni statistici dal tuo binario di rilascio senza strumentazione. Eseguire il file binario esistente, profilarlo usando xperf, convertire la traccia in un file SPD e ricompilare. Non c'è nessuna compilazione instrumentata e nessun rallentamento durante la profilatura. Il compilatore ottiene dati di campionamento statistici anziché conteggi esatti, meno precisi ma più facili da ottenere e non richiede modifiche al codice. Consente anche la profilatura dei componenti di sistema o dei componenti in tempo reale difficili da raccogliere con un approccio instrumentato. È anche possibile profilatura di file binari finali/di spedizione.

Questa esercitazione illustra tre metodi di profilatura: LBR, PMC e timer del sistema operativo. Scegliere il metodo in Scegliere il metodo di profilatura. Per un confronto dettagliato del normale processo di compilazione rispetto al processo di compilazione SPGO, inclusa una tabella di riferimento di flag, vedere Confronto tra processi di compilazione.

Configura perfcore.ini

⚠️ Obbligatorio: senza questo passaggio, xperf non fornisce i dati di profilatura necessari. Completare questo passaggio prima di eseguire xperf.

Il Windows Performance Toolkit (WPT) usa perfcore.ini, che si trova se il WPT è stato installato nel percorso predefinito in C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\perfcore.ini, per registrare i provider DLL necessari per SPGO.

Aprire Windows Blocco note come amministratore. Quindi aprire perfcore.ini. Trova la sezione dell'elenco delle DLL e aggiungi le voci seguenti, una per riga:

perf_spt.dll
perf_lbr.dll

Se xperf.exe non è installato, vedere Problemi generali per installarlo.

Salvare e chiudere perfcore.ini. I file DLL sono già disponibili nella stessa directory, xperf.exe in modo che non sia necessario copiarli da nessuna parte. Li stai registrando solo in perfcore.ini. Assicurarsi che xperf sia nel percorso.

Creare l'app di esempio

L'app di esempio per questa esercitazione è un programma C++ che legge il testo dall'input standard e produce un conteggio delle righe, il conteggio delle parole, il numero totale di caratteri, una tabella di frequenza dei caratteri e il tempo trascorso per elaborare il file in millisecondi. È scritto in C++, ma SPGO funziona anche con C. Il flusso di lavoro è identico per i progetti C.

Creare un file denominato textCount.cpp nella directory di lavoro e aggiungere il codice sorgente seguente:

// textCount.cpp : Text Statistics Counter
// Counts words, lines, and character frequencies from standard input
// Usage: textCount < file.txt

#include <iostream>
#include <string>
#include <map>
#include <cctype>
#include <chrono>

int main()
{
    auto start = std::chrono::steady_clock::now();

    std::map<unsigned char, int> charFrequency;
    int wordCount = 0;
    int lineCount = 0;
    int totalChars = 0;

    std::string line;
    bool inWord = false;

    while (std::getline(std::cin, line))
    {
        lineCount++;

        for (char c : line)
        {
            totalChars++;
            unsigned char uc = static_cast<unsigned char>(c);
            charFrequency[uc]++;

            if (std::isspace(static_cast<unsigned char>(c)))
            {
                inWord = false;
            }
            else
            {
                if (!inWord)
                {
                    wordCount++;
                    inWord = true;
                }
            }
        }

        inWord = false;
    }

    std::cout << "\n=== TEXT STATISTICS ===" << std::endl;
    std::cout << "Lines: " << lineCount << std::endl;
    std::cout << "Words: " << wordCount << std::endl;
    std::cout << "Total Characters: " << totalChars << std::endl;

    std::cout << "\n=== CHARACTER FREQUENCIES ===" << std::endl;

    std::cout << "\nLetters:" << std::endl;
    for (unsigned char ch = 'a'; ch <= 'z'; ch++)
    {
        unsigned char upperCh = static_cast<unsigned char>(std::toupper(ch));
        int count = charFrequency[ch] + charFrequency[upperCh];
        if (count > 0)
        {
            std::cout << static_cast<char>(ch) << ": " << count << std::endl;
        }
    }

    std::cout << "\nDigits:" << std::endl;
    for (unsigned char ch = '0'; ch <= '9'; ch++)
    {
        if (charFrequency[ch] > 0)
        {
            std::cout << static_cast<char>(ch) << ": " << charFrequency[ch] << std::endl;
        }
    }

    std::cout << "\nSpecial Characters:" << std::endl;
    for (const auto& pair : charFrequency)
    {
        unsigned char ch = pair.first;
        if (!std::isalnum(ch))
        {
            std::string displayChar;
            switch (ch)
            {
                case ' ': displayChar = "[space]"; break;
                case '\t': displayChar = "[tab]"; break;
                case '\n': displayChar = "[newline]"; break;
                case '\r': displayChar = "[return]"; break;
                default:
                    if (ch >= 32 && ch < 127)
                    {
                        displayChar = std::string(1, static_cast<char>(ch));
                    }
                    else
                    {
                        displayChar = "[byte:" + std::to_string(static_cast<int>(ch)) + "]";
                    }
                    break;
            }
            std::cout << displayChar << ": " << pair.second << std::endl;
        }
    }

    auto end = std::chrono::steady_clock::now();

    auto elapsed = std::chrono::duration<double, std::milli>(end - start);
    std::cout << "Elapsed time: " << std::fixed;
    std::cout.precision(3);
    std::cout << elapsed.count() << " ms\n";

    return 0;
}

Compilare ed eseguire l'esempio per ottenere una baseline

Prima di applicare SPGO, compilare textCount ed eseguirlo su un file di testo di grandi dimensioni, ad esempio War e Peace (è possibile scaricarlo da Project Gutenberg), per vedere come viene eseguito rapidamente. Questo passaggio mostra le prestazioni prima di ottimizzarlo usando SPGO:

Costruire:

cl /EHsc /GL /O2 textCount.cpp

Esegui:

textCount.exe < warAndPeace.txt

Viene visualizzato un output simile al seguente:

=== TEXT STATISTICS ===
Lines: 66041
Words: 566333
Total Characters: 3227531

=== CHARACTER FREQUENCIES ===

Letters:
a: 202719
...

Elapsed time: 512.000 ms

Registrare il valore Elapsed time. Verrà confrontato con il tempo ottimizzato per SPGO in Misurare i risultati.

Generare textCount con /spgo

Compila ora textCount con SPGO abilitato. Questo passaggio consente di raccogliere i dati di profilatura.

cl /EHsc /GL /O2 textCount.cpp /link /debug /spgo

Al termine della compilazione, viene visualizzato un messaggio simile al seguente:

SPD textCount.spd not found, compiling without profile guided optimizations

Questo messaggio viene visualizzato nella prima /spgo compilazione. Il linker crea il file SPD, ma è ancora vuoto, quindi non applica ancora alcuna ottimizzazione SPGO. Dopo aver eseguito il file binario, raccogliere i dati del profilo e convertirli in SPD, questo messaggio non verrà visualizzato.

Spiegazioni dei flag:

Flag Scopo
/EHsc Abilitare la gestione delle eccezioni C++
/GL Ottimizzazione dell'intero programma, necessaria per SPGO. Rinvia l'ottimizzazione finale alla fase di collegamento, consentendo decisioni di inlining tra moduli, layout del codice ed eliminazione del codice morto.
/O2 Ottimizza per la velocità — consente l'inlining aggressivo, l'ottimizzazione dei cicli, la rimozione del codice inutilizzato e altre trasformazioni correlate.
/link /debug Passa /debug al linker per generare informazioni di debug (.pdb), che xperf usa per associare i campioni di profilazione al codice sorgente.
/spgo Flag del linker SPGO: incorpora i metadati SPGO nel file binario e crea un file vuoto textCount.spd insieme all'eseguibile.

Note

/spgo è un flag del linker. Passalo al linker tramite /link /spgo nel comando cl.

Il /spgo flag non ottimizza ancora il file binario. Lo prepara per la profilazione. L'ottimizzazione avviene in Rigenera textCount tramite /spdin dopo che l'SPD è stato popolato con dati di runtime effettivi.

Note

Per scrivere l'SPD in una posizione specifica, aggiungere il flag facoltativo del linker /spd:<path>. Ad esempio: /link /debug /spgo /spd:.\profiles\textCount.spd. Se si omette questo flag, lo SPD viene creato insieme a .exe.

Scegliere il metodo di profilatura

SPGO supporta tre metodi di profilatura. Il metodo usato dipende dall'hardware.

I tre metodi di profilatura

metodo Qualità del campione Requisito hardware Ideale per
LBR (ultimo record di ramo) Massimo—registra sequenze di diramazioni eseguite di recente, fornendo all'ottimizzatore dati dettagliati sul flusso di controllo per ogni campione Intel Haswell (2013) o versione successiva; AMD Zen 4 (2022) o versione successiva; ARM64 ARMv9.2-A (2020) o versione successiva Hardware desktop più moderno
Modalità PMC / IP (contatore di monitoraggio delle prestazioni/puntatore di istruzione) Buone. Acquisisce esempi di puntatore a istruzioni con stack di chiamate usando l'unità di monitoraggio delle prestazioni (PMU) della CPU, raccolta tramite Event Tracing for Windows (ETW) Qualsiasi CPU x64 o ARM64 con pmU Hardware senza supporto LBR
Timer del sistema operativo Di base: esempi basati su timer Qualsiasi CPU x64 o ARM64, macchine virtuali senza pass-through PMU Macchine virtuali e hardware meno recente

In modalità PMC/IP, ogni interrupt hardware fornisce un solo dato: "la CPU si trovava all'indirizzo 0x1A2B3C4D quando si è verificato l'interrupt". Con LBR, ogni interruzione fornisce uno stack degli ultimi 16–32 salti eseguiti dalla CPU prima che si verificasse l'interruzione. L'ottimizzatore dispone di dati migliori sul flusso di controllo e può effettuare scelte migliori in merito all'inlining e alla disposizione.

Rileva il tuo percorso

Eseguire i due comandi seguenti per determinare il percorso di profilatura supportato dal computer. Questi comandi non richiedono un prompt con privilegi elevati.

Passaggio 1: Verificare il supporto LBR. Questo test funziona su Intel/AMD/ARM64.

Eseguire il comando seguente da un prompt dei comandi per sviluppatori administrator Visual Studio:

xperf.exe -on PMC_PROFILE -pmcprofile TotalIssues -LastBranch PmcInterrupt -setProfInt TotalIssues 2560000
xperf -stop -d lbrtest.etl
xperf -tle -i lbrtest.etl -a dumper | findstr "LBR,  TimeStamp"
  • Se questo comando trova una riga contenente LBR, TimeStamp, il computer supporta LBR. Usare il percorso LBR.
  • In caso contrario, continuare con il passaggio 2.

Passaggio 2: Verificare la disponibilità del supporto PMC (senza LBR)

xperf.exe -pmcsources | findstr TotalIssues
  • Se questo comando produce output, il computer supporta contatori PMC ma non LBR. Usare il percorso PMC.
  • Se questo comando non produce alcun risultato, allora utilizzare il percorso del timer del sistema operativo.

Per altre informazioni sulla raccolta di eventi PMU con xperf, vedere Registrazione di eventi PMU hardware con xperf.

Tabella delle decisioni

LBR, TimeStamp risultato Output TotalIssues Il tuo percorso
Non vuoto (non selezionato) LBR
Vuoto Non vuoto PMC
Vuoto Vuoto Timer del sistema operativo
Processore ARM64 N/A PMC (se la PMU è disponibile) o timer del sistema operativo

Scegliere l'approccio

Decidere se usare il percorso del timer LBR, PMC o del sistema operativo in base ai risultati del rilevamento. Ogni percorso ha parametri di inizio diversi xperf per raccogliere i dati di profilatura appropriati. Seguire il percorso corrispondente alle funzionalità hardware.

Percorso:

Tutti i passaggi convergono in Eseguire il carico di lavoro e arrestare xperf.

I comandi in questa sezione dipendono dal percorso di profilatura identificato in Scegliere il metodo di profilatura. Individuare la sottosezione che corrisponde al proprio percorso, eseguire il comando xperf start e quindi continuare con Eseguire il carico di lavoro e arrestare xperf per eseguire il carico di lavoro e arrestare xperf.

⚠️ Esegui come amministratore:xperf richiede un prompt dei comandi per gli sviluppatori con privilegi elevati (amministratore). Senza elevazione, xperf restituisce "failed to configure counters".

Percorso LBR

Inizia xperf con la collezione LBR:

xperf -on LOADER+PROC_THREAD+PMC_PROFILE -MinBuffers 4096 -MaxBuffers 4096 -BufferSize 4096 -pmcprofile BranchInstructionRetired -LastBranch PmcInterrupt -setProfInt BranchInstructionRetired 16384

Spiegazione dei parametri:

Parametro Scopo
LOADER+PROC_THREAD+PMC_PROFILE Provider del kernel: eventi del caricatore (mapping dei moduli), eventi di processo/thread (contesto di esecuzione) ed eventi di profilatura PMC
-MinBuffers 4096 -MaxBuffers 4096 -BufferSize 4096 Buffer circolari di grandi dimensioni per evitare campioni persi nel corso di un'intera esecuzione di Guerra e pace
-pmcprofile BranchInstructionRetired Trigger evento PMC: genera un campione a ogni N-esima istruzione di diramazione ritirata
-LastBranch PmcInterrupt Abilita la registrazione hardware dell'LBR: a ogni interrupt PMC, acquisisce lo stack dei record hardware degli ultimi rami
-setProfInt BranchInstructionRetired 16384 Intervallo di esempio: genera un interrupt ogni 16.384 istruzioni del ramo ritirato

Dopo aver avviato xperf, procedere con Eseguire il carico di lavoro e arrestare xperf.

Percorso PMC (senza LBR)

Avviare xperf con la raccolta in modalità PMC/IP:

xperf -on LOADER+PROC_THREAD+PMC_PROFILE+PROFILE -MinBuffers 4096 -BufferSize 4096 -pmcprofile InstructionRetired -setProfInt InstructionRetired 16384 -stackwalk profile

Spiegazione dei parametri:

Parametro Scopo
LOADER+PROC_THREAD+PMC_PROFILE+PROFILE Aggiunge PROFILE (campionamento CPU) e PMC_PROFILE per gli eventi PMC; no -LastBranch
-pmcprofile InstructionRetired Trigger di evento PMC: esempio per istruzioni ritirati (modalità puntatore alle istruzioni)
-setProfInt InstructionRetired 16384 Genera un'interruzione ogni 16.384 istruzioni completate
-stackwalk profile Catturare uno stack di chiamate a ogni interrupt di profiling, fornendo dati sulla catena di chiamate anziché sequenze di diramazioni

Rispetto a LBR: nessun -LastBranch flag; usa InstructionRetired anziché BranchInstructionRetired. Il risultato è esempi di puntatore a istruzioni con stack di chiamate, non sequenze di rami. Questo percorso fornisce ancora dati efficaci per l'ottimizzatore, ma è leggermente meno ricco.

Dopo aver avviato xperf, passare a eseguire il carico di lavoro e arrestare xperf.

Percorso del timer del sistema operativo

Avvia xperf con il campionamento basato sul timer del sistema operativo:

xperf -on LOADER+PROC_THREAD+PROFILE -MinBuffers 4096 -BufferSize 4096 -setProfInt Timer 1221 -stackwalk profile

Spiegazione dei parametri:

Parametro Scopo
LOADER+PROC_THREAD+PROFILE Nessun evento PMC; campionamento della CPU solo tramite interrupt del timer del sistema operativo
-setProfInt Timer 1221 Generare l'interruzione del timer del sistema operativo ogni 1.221 tick del timer (circa 1 kHz)
-stackwalk profile Acquisire uno stack di chiamata a ogni interrupt del timer

Rispetto a LBR e PMC, questo metodo non usa contatori delle prestazioni hardware. Il timer del sistema operativo viene attivato a intervalli di tempo approssimativamente fissi indipendentemente dall'attività della CPU. I campioni sono correlati in modo meno stretto al codice eseguito più frequentemente, ma forniscono comunque dati utili sul controllo di flusso per l'ottimizzatore.

Eseguire il carico di lavoro e arrestare xperf (tutti i percorsi)

Con xperf in esecuzione, esegui textCount su Guerra e pace:

textCount.exe < warAndPeace.txt

Dopo che textCount ha terminato, arresta xperf e scrivi il file di traccia. Consentire l'esecuzione di altri processi durante la profilatura diluisce la qualità del campione. Per ottenere risultati ottimali, chiudere le applicazioni non necessarie prima di eseguire il carico di lavoro.

xperf -stop -d textCount.etl

Dopo aver arrestato xperf (la scrittura del file etl può richiedere un po' di tempo), verificare che textCount.etl sia stato creato nella directory corrente.

Convertire il file ETL in SPT

Questo passaggio è lo stesso per tutti e tre i percorsi di profilatura.

Eseguire SPTAggregate.exe per elaborare la traccia ETL non elaborata e creare un file di profilo SPT:

SPTAggregate.exe /binary textCount.exe /etl textCount.etl textCount.spt

Spiegazione dei parametri:

Parametro Scopo
/binary textCount.exe File binario da cui estrarre campioni. L'ETL può contenere campioni di tutti i processi eseguiti durante la profilatura
/etl textCount.etl File di traccia ETL di input
textCount.spt File di output del profilo SPT

SPTAggregate restituisce un riepilogo che mostra il numero di campioni raccolti. Questo riepilogo è la prima conferma che la profilazione ha funzionato.

Confronta l'output di SPTAggregate con il percorso seguito:

  • Percorso LBR: Cercare un conteggio degli esempi LBR non usata da zero.
  • Percorso PMC: Cerca un numero di campioni PMC o dello stack diverso da zero.
  • Percorso del timer del sistema operativo: Cercare un conteggio dei campioni di stack utilizzati diverso da zero.

Se tutti i conteggi sono pari a zero, vedere Risoluzione dei problemi prima di continuare.

Convertire il file SPT in SPD

Percorso:

Entrambi i percorsi dei timer PMC e del sistema operativo usano /mode:IP perché entrambi producono campioni del puntatore di istruzione.

Il passaggio successivo si dirama in base al percorso di profilazione, nello specifico in base al flag /mode passato a SPDConvert.exe.

Modalità LBR

SPDConvert.exe /mode:LBR textCount.spd textCount.spt

/mode:LBR indica a SPDConvert di interpretare lo SPT come contenente i dati della sequenza di diramazione LBR.

Modalità IP (PMC e timer del sistema operativo)

Sia PMC sia il timer del sistema operativo producono campioni del puntatore di istruzione, quindi entrambi utilizzano lo stesso comando di conversione:

SPDConvert.exe /mode:IP textCount.spd textCount.spt

/mode:IP indica a SPDConvert di interpretare l'SPT come contenente campioni del puntatore di istruzione.

Avvertimento

L'uso della modalità errata per il tipo di dati può produrre un SPD vuoto o in formato non valido. Se hai eseguito la profilazione con LBR, usa /mode:LBR. Se è stata eseguita la profilazione con PMC o con il timer del sistema operativo, utilizzare /mode:IP. L'output SPTAggregate di riepilogo di Convert the ETL file to SPT (Converti il file ETL in SPT ) mostra quali tipi di esempio sono stati raccolti e conferma la modalità corretta da usare.

Dopo aver eseguito SPDConvert, verifica che textCount.spd sia stato creato (o aggiornato) nella directory corrente.

Interpretazione dell'output di SPDConvert

Il comando SPDConvert textCount.spd textCount.spt stampa un riepilogo della copertura dei blocchi prima e dopo, ad esempio:

Block coverage (before) : 33.90% ( 4507/ 13294)
Block coverage (after)  : 45.64% ( 6067/ 13294)

Questo riepilogo mostra la percentuale dei blocchi di codice del file binario associati ai dati del profilo. Una percentuale maggiore è migliore. La copertura superiore a 70% è eccellente, mentre la copertura al di sotto di 40% potrebbe limitare l'efficacia dell'ottimizzazione. Se la copertura è bassa, eseguire il carico di lavoro di profilatura più lungo o combinare più file SPT da esecuzioni separate con carichi di lavoro diversi. Ad esempio, è possibile eseguire textCount su più file di testo per eseguire percorsi di codice diversi.

Potrebbe essere visualizzato un avviso SPDConvert simile al seguente:

Compiler may be conservative on some hot functions due to sparse sample coverage.
SPGO is estimated to optimize better if sample density is increased to 5.4x of current level.
Sample density can be increased by sampling for longer period, or increasing sample rate.

Questo avviso indica che l'esecuzione di profilazione non ha raccolto campioni sufficienti perché l'ottimizzatore possa ottimizzare con sufficiente affidabilità tutte le funzioni più utilizzate. L'SPD è ancora utilizzabile, ma è possibile migliorare i risultati in base a:

  • Esecuzione del carico di lavoro più lungo (ad esempio, 5 o più minuti invece di 1 minuto) o uso di carichi di lavoro diversi.
  • Riduzione del -setProfInt valore nel xperf comando per aumentare la frequenza di campionamento. Il compromesso è che questa modifica produce un file ETL più grande, che richiede più tempo per l'elaborazione.
  • Combinando più file SPT da esecuzioni di profilatura separate passandole tutte a SPDConvert.

Il file SPT è un formato binario. Per esaminarne il contenuto, è possibile eseguire SPTDump.exe textCount.spt. Analogamente, PTDump.exe textCount.spt mostra i dati del profilo compilati dopo l'esecuzione di SPDConvert. Entrambi gli strumenti sono utili per verificare campioni diversi da zero prima di procedere.

Ricostruire textCount con /spdin

Ricostruire textCount utilizzando il file SPD popolato. Il linker legge i dati del profilo e applica le ottimizzazioni SPGO.

Questo passaggio è lo stesso per tutti e tre i percorsi di profilatura.

cl /EHsc /GL /O2 textCount.cpp /link /debug /spgo /spdin:textCount.spd

Nuovo flag (rispetto a Build textCount con /spgo):

Flag Scopo
/spdin:textCount.spd Fornire i dati del profilo SPD al linker per l'ottimizzazione

Il comando include ancora /spgo. Genera un nuovo file SPD insieme al file binario ottimizzato, che è possibile usare come punto di partenza per le iterazioni di profilatura successive.

Avvertimento

Il file SPD è associato al binario esatto che profila. Se ricompili textCount senza /spdin, oppure a partire dal codice sorgente modificato, devi generare un nuovo file SPD. Quello esistente non corrisponde al GUID del nuovo file binario e il linker non lo userà.

Dopo la ricompilazione con /spdin, il linker produce statistiche su quanto del codice è stato ottimizzato utilizzando i dati di profiling. Per esempio:

221 of 221 (100.00%) profiled functions will be compiled for speed
201 of 1383 inline instances were from dead/cold paths
474 of 474 profiled functions (100.0%) were optimized using profile data
202738780 of 202738780 instructions (100.0%) were optimized using profile data

Una percentuale elevata indica che l'SPD copre l'area binaria. Se la percentuale è bassa (ad esempio, al di sotto di 90%), il carico di lavoro di profilatura non ha eseguito un esercizio sufficiente del file binario oppure il file binario è cambiato in modo significativo dopo la raccolta del profilo. In entrambi i casi, eseguire il reprofile rispetto al file binario corrente.

Cosa fa SPGO con i dati del profilo

SPGO usa i dati campione raccolti per assegnare i conteggi a ogni blocco e arco nel grafo del flusso di controllo del programma. Questi conteggi consentono ottimizzazioni come le seguenti:

  • Inlining guidato dal profilo: integra in modo aggressivo i siti di chiamata usati più frequentemente, evitando al contempo il gonfiamento del codice dovuto all'inlining dei percorsi eseguiti raramente.
  • Separazione del codice hot/cold: consiste nello spostare il codice eseguito raramente in sezioni separate del file binario, migliorando l'utilizzo della cache delle istruzioni e il comportamento della paginazione.
  • Layout delle funzioni: posizionare nel file binario, una vicino all'altra, le funzioni che si chiamano frequentemente, riducendo i page fault e migliorando la località dei riferimenti. Le funzioni ottimizzate sono organizzate in gruppi COFF di affinità elevata nel file binario.
  • Decisioni su dimensione/velocità: compilare le funzioni usate più frequentemente per la velocità e quelle usate meno frequentemente per le dimensioni. Le routine senza hit nel profilo osservato potrebbero essere compilate ottimizzando le dimensioni anziché la velocità, limitando ottimizzazioni come l'inlining e lo srotolamento dei cicli in quei percorsi eseguiti raramente.
  • Devirtualizzazione speculativa: quando il campionamento rivela che una chiamata indiretta punta costantemente alla stessa funzione, SPGO può ipotizzare quel target e integrarla inline, con un meccanismo di fallback per il caso raro.

Misurare i risultati

Eseguire textCount di nuovo e confrontare i tempi trascorsi.

textCount.exe < warAndPeace.txt

Raccogliere più esecuzioni per ogni configurazione e utilizzare la mediana. Una singola esecuzione non è affidabile perché la pianificazione del sistema operativo e il rumore di sistema possono falsare le singole misurazioni.

Costruire Tempo trascorso rappresentativo
Baseline (cl /EHsc /O2) (la tua misura)
/spgo build (nessun dato del profilo ancora disponibile) (deve essere vicino alla linea di base)
ottimizzato per SPGO (/spdin) (dovrebbe mostrare un miglioramento)

In un solo test, SPGO che usa il metodo LBR ha fornito circa 7% riduzione del tempo trascorso. I risultati possono variare a seconda dei propri progetti, perché i vantaggi di SPGO dipendono da quanto bene il carico di lavoro usato per la profilazione rappresenta l'esecuzione tipica. Le codebase più grandi e con molti rami tendono a registrare miglioramenti maggiori nell’intervallo del 5–10%. Il metodo di profilatura influisce sulla qualità dell'ottimizzazione. LBR produce in genere risultati migliori rispetto a PMC, che produce risultati migliori rispetto al timer del sistema operativo. Se si è nel percorso timer del sistema operativo, aspettatevi guadagni più piccoli.

Il percorso LBR seguito in questa esercitazione è stato applicato al progetto SQLite , ovvero una libreria di database di produzione. Il file binario SQLite ottimizzato per SPGO ha mostrato circa 7% miglioramento.

Applicare SPGO al proprio progetto

Usare questo elenco di controllo per applicare SPGO alla propria applicazione C o C++.

  1. Aggiungere /link /spgo al comando build di versione esistente. Modificare lo script di compilazione o il file di progetto:

    cl /EHsc /GL /O2 myapp.cpp /link /spgo
    
  2. Scegliere un carico di lavoro rappresentativo. Selezionare uno scenario di utilizzo reale che metta alla prova i percorsi più utilizzati dell'applicazione. Usa dati simili a quelli di produzione. Evita quanto segue come carico di lavoro principale per la profilazione: test di copertura del codice (non mettono sotto stress i colli di bottiglia delle prestazioni), percorsi di errore non comuni, fasi di avvio e arresto e percorsi di codice deprecati. Questo carico di lavoro genera il profilo che alimenta l'ottimizzatore.

  3. Eseguire xperf usando il percorso rilevato. Usare il percorso identificato in Scegliere il metodo di profilatura (LBR, PMC o timer del sistema operativo). Avviare xperf, eseguire il carico di lavoro una volta, arrestare xperfe acquisire il file ETL.

  4. Per il percorso del timer PMC o del sistema operativo, eseguire SPTAggregate e SPDConvert con il flag /mode corretto. Convertire ETL in SPT e quindi in SPD. Usare /mode:LBR per i dati LBR; usare /mode:IP per i dati del timer PMC o del timer del sistema operativo.

  5. Ricostruisci con /spdin:<your-spd-path>. Compila la tua applicazione con lo SPD compilato:

    cl /EHsc /GL /O2 yourApp.cpp /link /spgo /spdin:yourApp.spd
    
  6. Misurare prima e dopo. Eseguire il carico di lavoro sia con i file binari non ottimizzati sia con quelli ottimizzati per SPGO. Raccogli la mediana di più esecuzioni per ogni configurazione. Una singola esecuzione non è affidabile per il benchmarking.

  7. Archiviare il .spd file nel controllo del codice sorgente. Controllare il .spd file nel sistema di controllo del codice sorgente insieme al codice sorgente.

  8. Abilitare SPGO nelle build della versione per sviluppatori. Fai in modo che le build di rilascio del tuo team usino gli stessi binari ottimizzati per SPGO della produzione. In questo modo è possibile rilevare le regressioni delle prestazioni in anticipo.

  9. Disabilitare SPGO nelle compilazioni di debug.

  10. Monitora le statistiche di completezza del profilo del linker. Dopo ogni compilazione con /spgo, prendere nota della percentuale di funzioni profilate ottimizzate usando i dati del profilo. Se questo valore scende significativamente (inferiore a 90%), ripetere il file binario corrente. Le modifiche al codice si accumulano e il spd può diventare obsoleto.

Alternativa all'uso di xperf

Un altro modo per raccogliere i dati del profilo consiste nell'usare un profiler di campionamento come Windows Performance Recorder (WPR). WPR viene installato per impostazione predefinita in Windows 10 e versioni successive. Raccoglie dati simili a xperf. È possibile configurare WPR per raccogliere campioni della CPU con gli stack di chiamate e quindi esportare i dati in un file ETL, che è possibile elaborare con SPTAggregate e SPDConvert, come il file ETL xperf. Ecco un esempio di uso di WPR per raccogliere i dati del profilo:

wpr -start CPU.light -filemode
textCount.exe < warAndPeace.txt
wpr -stop spgo_data.etl

Per altre informazioni sull'uso di WPR, vedere Using Windows Performance Recorder.

Distribuzione SPD

È possibile:

  • Controllare il .spd file direttamente nel controllo del codice sorgente insieme al codice sorgente.
  • Condividi il file .spd con i colleghi in modo che possano eseguire la compilazione con le ottimizzazioni SPGO senza ripetere la profilazione.
  • Includi il file .spd insieme ai file binari come artefatto con versione (ad esempio, un pacchetto NuGet) e registra quale versione corrisponde a quali file binari.
  • Rigenerare il .spd file in qualsiasi momento ripetendo il flusso di lavoro di profilatura.

Lo SPD è collegato all'esatto binario da cui è stato compilato. Dopo modifiche significative al codice, riprofili per generare un nuovo SPD. Durante la /spdin compilazione, il compilatore produce anche un nuovo .spd file. Salva questo nuovo SPD come artefatto di build: è il punto di partenza per la prossima iterazione di profilazione.

Riutilizzo delle informazioni SPD tra le compilazioni

Il concetto "porta avanti" in SPGO consente di aggiungere dati di profilatura a un file SPD esistente senza profilare nuovamente tutti gli scenari da zero e senza perdere informazioni sul profilo esistenti. È anche possibile modificare il peso da assegnare ai dati del profilo meno recenti. Questa flessibilità è utile quando potrebbero esserci modifiche comportamentali nel tempo e non si vuole perdere completamente le informazioni di profilatura rispetto alle esecuzioni precedenti dello scenario. Ad esempio, una DLL potrebbe ricevere chiamate a API diverse man mano che l'applicazione che la chiama si evolve. Si vogliono ancora le ottimizzazioni basate su come si comportava in passato, ma si vogliono anche integrare opportunità di ottimizzazione per i casi in cui ora si comporta talvolta in modo diverso. È possibile evolvere il profilo nel tempo combinando dati vecchi e nuovi.

Quando si esegue SPDConvert con un nuovo file SPT, specificare il nome del file SPD esistente. Utilizzare quindi l'opzione /retire:N per controllare quanto aggressivamente SPDConvert riduce l'importanza dei dati del profilo meno recenti quando si aggiungono nuovi file SPT:

  • Il valore predefinito (/retire:8) pesa i dati più recenti in modo più pesante.
  • Utilizzare /retire:0 per assegnare lo stesso peso a tutte le esecuzioni.
  • Usare /retire:16 per consentire solo il conteggio dei dati più recente.

Risoluzione dei problemi

Trovare il problema:

Problemi del percorso LBR

Problema Causa possibile Correzione
Nessun campione LBR nell'output SPTAggregate La CPU non supporta LBR o la macchina virtuale non espone LBR Esegui il comando per il rilevamento in Detect your path. Se in una macchina virtuale Hyper-V, eseguire Set-VMProcessor MyVMName -Perfmon @("pmu", "lbr") nell'host. Se LBR non è disponibile, passare al percorso PMC o a quello del timer del sistema operativo.
Il processore supporta LBR ma SPTAggregate mostra 0 esempi LBR perfcore.ini Registrazione DLL incompleta Completa la configurazione perfcore.ini in Configurare perfcore.ini. Assicurarsi che perf_lbr.dll sia registrato.
SPDConvert ha esito negativo o produce un SPD vuoto Flag errato /mode o SPT contiene solo esempi in modalità IP Verificare che SPTAggregate l'output mostrasse campioni LBR. Se l'output mostra solo esempi in modalità IP, passare a /mode:IP.

Problemi relativi al percorso PMC

Problema Causa possibile Correzione
Zero campioni PMC nell'output SPTAggregate perfcore.ini Registrazione dll non corretta Completa la configurazione perfcore.ini in Configurare perfcore.ini. Assicurarsi che perf_spt.dll sia registrato. Senza questa DLL, xperf produce zero campioni PMC senza un messaggio di errore.
Eseguire xperf.exe -pmcsources per visualizzare l'elenco delle origini dei contatori delle prestazioni disponibili nella CPU. Se non vengono visualizzate voci come SPT_OP_RETIRE_INSTR o SPT_OP_RETIRE_BR_INSTR o SPT_OP_ETW_INSTR, la registrazione della DLL in perfcore.ini potrebbe essere incompleta o la CPU potrebbe non supportare PMC. Se non è possibile risolvere la registrazione della DLL, provare invece il percorso timer del sistema operativo.
findstr InstructionRetired restituisce l'output ma xperf non produce campioni Mascheramento dei contatori PMC della VM Controllare se è in esecuzione in una macchina virtuale. Abilita PMU in Hyper-V con Set-VMProcessor oppure passa al percorso del timer del sistema operativo.
SPDConvert ha esito negativo nel percorso PMC Uso di /mode:LBR su un SPT solo IP Passa a /mode:IP.

Problemi relativi al percorso timer del sistema operativo

Problema Causa possibile Correzione
Miglioramento inferiore al previsto Previsto: il timer del sistema operativo è meno preciso È normale. L'ottimizzatore dispone di meno informazioni sul flusso dei rami dai campioni del timer rispetto a quelle ricavate da LBR o PMC. I miglioramenti delle prestazioni sono inferiori. Valutare la possibilità di eseguire l'aggiornamento a PMC o LBR se l'hardware lo supporta.
Esempi di timer zero xperf non è stato eseguito in un prompt con privilegi elevati o PROFILE il provider mancante Confermare l'esecuzione come amministratore. Verificare che -stackwalk profile sia stato fornito al comando xperf.

Problemi generali (tutti i percorsi)

Problema Causa possibile Correzione
"failed to configure counters" Errore xperf non in esecuzione come amministratore Riavviare il prompt dei comandi come amministratore (fare clic con il pulsante destro del mouse su > Esegui come amministratore). xperf richiede privilegi elevati per configurare i contatori delle prestazioni hardware.
xperf non trovato xperf.exe non è nel PATH Verificare che il Windows ADK sia installato. Controllare C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\. Aggiungere tale directory al PATH oppure eseguire xperf direttamente da essa.
textCount.etl non creato xperf non è riuscito in modo invisibile all'utente Confermare l'esecuzione come amministratore. Riesegui il comando xperf start e controlla se viene visualizzato un messaggio di errore.
SPTAggregate non riesce con "binario non trovato" textCount.exe non presente nella directory corrente o percorso errato Assicurarsi di trovarsi nella directory in cui si trova textCount.exe, oppure specificare il percorso completo del parametro /binary.
File SPD non creato SPDConvert non riuscito Verificare che la dimensione di textCount.spt sia diversa da zero. Eseguire SPTDump.exe textCount.spt per esaminarne il contenuto.
/spdin la compilazione non produce alcun miglioramento Mancata corrispondenza tra GUID/age di SPD e binario L'SPD è stato creato da un oggetto diverso textCount.exe. Profilare nuovamente la compilazione corrente per generare un nuovo SPD.
Errore di versione MSVC in /spgo Set di strumenti MSVC precedente alla versione 14.51 Apri Visual Studio Installer >Individual Components> e installa MSVC v14.51 o una versione successiva. Riaprire il prompt dei comandi per gli sviluppatori.

Passaggi successivi

Dopo aver completato questa esercitazione, esplorare queste funzionalità per ottenere altre informazioni da SPGO:

  • Fusione dei profili: Eseguire più carichi di lavoro, accumulare file SPT da ogni esecuzione e passarli tutti a SPDConvert. Un SPD misto riflette l'intera gamma di modelli di utilizzo reali e produce ottimizzazioni migliori rispetto a un profilo a singolo scenario. Usare l'opzione /retire:N per controllare quanto aggressivamente SPDConvert riduce l'importanza dei dati del profilo meno recenti quando si aggiungono nuovi file SPT. Il valore predefinito (/retire:8) pesa i dati più recenti in modo più pesante. Usare /retire:0 per assegnare lo stesso peso a tutte le esecuzioni; usare /retire:16 per far contare solo i dati più recenti.
  • I risultati migliori provengono dalla fusione di profili da più origini, ad esempio benchmark che sottolineano scenari chiave e dati reali (se disponibili). Passa i file SPT da tutte le fonti a SPDConvert. Ripetere un file SPT nella lista degli argomenti per attribuirgli maggiore peso (ad esempio, SPDConvert myapp.spd critical.spt critical.spt common.spt attribuisce a critical.spt un peso doppio rispetto a common.spt).
  • Ottimizzazione iterativa: Ogni ricompilazione con /spdin produce un nuovo SPD. È possibile ripetere il ciclo di esecuzione, profilazione e ricompilazione. Le iterazioni successive potrebbero mostrare rendimenti decrescenti, ma un secondo passaggio può talvolta cogliere schemi che al primo sono sfuggiti.
  • Modifiche al codice: Dopo modifiche significative al codice sorgente, raccogliere nuovamente i dati di profilazione. Lo SPD esistente è legato al file binario su cui è stata effettuata la profilazione. Non corrisponderà a un file binario ricompilato in modo sostanziale.
  • Aggiornamento del profilo: Il linker segnala la percentuale di funzioni profilate ottimizzate usando i dati del profilo dopo ogni /spdin compilazione. Se questa percentuale scende significativamente, si tratta di un segnale che il codice è divergente dal profilo. Riprofili il file binario corrente.