Use a Otimização Guiada por Perfis de Exemplo (SPGO) para melhorar o desempenho em C++

A Otimização guiada por perfis (PGO) utiliza dados em tempo de execução para ajudar o compilador a fazer melhores escolhas de otimização. Ao utilizar dados de perfil de execução recolhidos de cargas de trabalho representativas, o PGO permite ao compilador tomar decisões mais inteligentes sobre inlining, layout de código e separação de código quente/frio. Estas decisões são impossíveis de tomar apenas com análise estática.

A SPGO adota uma abordagem diferente. Em vez de instrumentar o seu binário e executá-lo em cenários de treino sintético, o SPGO utiliza contadores de desempenho de hardware recolhidos dos binários de lançamento reais. Os processadores modernos fornecem capacidades de amostragem por hardware. Pode recolher estas amostras com uma sobrecarga de execução negligenciável, o que torna prático recolher perfis de execução diretamente do código de produção.

Como os perfis SPGO geram binários finais em vez de compilações instrumentadas, permitem muito mais flexibilidade na forma e no local onde recolhe dados. Pode recolher perfis de execução de servidores de produção, máquinas de programação, laboratórios de desempenho ou qualquer combinação. O resultado é um binário que executa caminhos quentes de forma mais eficiente, com uma velocidade típica de desempenho de 5-15% dependendo da qualidade dos dados do perfil.

Neste tutorial, percorre todo o fluxo de trabalho do SPGO: constrói uma aplicação de exemplo, perfila-a usando xperf, prepara os dados do perfil e reconstrói com os dados do perfil. Quando terminares, podes aplicar o mesmo processo aos teus próprios projetos.

Pré-requisitos

Antes de começar, certifique-se de que possui o seguinte software e hardware.

Programa informático

  • Ferramentas de compilação MSVC para x64/x86/ARM64 v14.51 ou superior—Instale-as através do Instalador do Visual Studio. Em Componentes Individuais, pesquise por "ferramentas de construção MSVC."
  • Windows Performance Toolkit (xperf.exe) — O profiler xperf recolhe dados de amostra durante a execução do seu programa. Descarregue o Windows Assessment and Deployment Kit (ADK) a partir da instalação ADK. Quando executares o instalador ADK, seleciona o componente Windows Performance Toolkit para obter xperf. Não precisas de instalar o ADK completo.
  • Ficheiro de texto Guerra e Paz — Usado como carga de trabalho de exemplo para gerar dados de perfil. Descarregue-o de Project Gutenberg: https://www.gutenberg.org/ebooks/2600. Guarde-o como um ficheiro de texto simples no seu diretório de trabalho.

Requisitos de Hardware

O tutorial tem três caminhos de perfilamento. O caminho que usas depende do teu hardware. Executa comandos de deteção em Escolha o seu método de perfilagem para descobrir que caminho a sua máquina suporta. Por agora, use esta tabela para confirmar que cumpre pelo menos um dos requisitos.

Path Requisito da CPU Notes
LBR (melhores resultados) Last Branch Records (LBR) são contadores de desempenho fornecidos em CPUs Intel Haswell (4.ª geração Core, 2013) ou posteriores; AMD Zen 4 (2022) ou posterior, ARM64 ARMv9.2-A (2020) ou posterior Fornece os melhores dados da filial. Para mais informações sobre o LBR, consulte Uma introdução aos registos da última ramificação
Modo PMC/IP (bons resultados) Contadores de Monitorização de Desempenho (PMC) são suportados em qualquer CPU x64 com unidade de monitorização de desempenho (PMU) Funciona na maioria dos CPUs modernos onde o LBR não está disponível. Para mais informações sobre PMC, consulte Eventos de Performance de Hardware de Gravação (PMU) e Eventos de Performance de Hardware de Gravação (PMU) com Exemplos Completos
Temporizador do sistema operativo (funciona em todo o lado) Qualquer CPU x64 ou ARM64, incluindo VMs e máquinas virtuais Azure Amostras de menor fidelidade, mas sempre disponíveis

A maioria dos desenvolvedores que utiliza computadores de secretária x64 modernos tem suporte para LBR. As VMs e algum hardware mais antigo têm PMC ou um temporizador do sistema operativo.

Como funciona o SPGO

O SPGO recolhe dados de perfil do teu binário em execução e envia-os de volta ao compilador na próxima compilação. O compilador utiliza esses dados para tomar decisões mais acertadas sobre a inclusão em linha, a organização do código e a previsão de desvios. Uma conveniência é que não é necessária instrumentação.

O fluxo de trabalho é:

  1. Compile o seu binário com a opção /spgo do linker. Este passo cria uma base de dados vazia de perfis de amostras (.spd ficheiro).
  2. Perfilar o binário usando xperf para produzir um ficheiro de traço ETL.
  3. Converta o ETL para um ficheiro SPT usando SPTAggregate.exe, depois converta o SPT para um ficheiro SPD usando SPDConvert.exe.
  4. Recompile com a opção /spdin do linker a apontar para a base de dados de perfis de exemplo (SPD) previamente preenchida. O linker aplica otimizações SPGO.

O otimizador usa o SPD para responder a perguntas como: quais ramificações são mais frequentes? Que funções são chamadas em hot loops? Este processo produz melhores decisões de layout de código e inlining do que a análise estática isolada.

O SPGO funciona tanto com C como com C++. O fluxo de trabalho e os flags são idênticos para ambas as línguas.

Melhores candidatos para SPGO: Grandes aplicações C/C++ com muitas ramificações e ciclos internos críticos. Ganha escala com o tamanho da base de código e a complexidade das ramificações. A pequena amostra neste tutorial mostra cerca de 7% melhoria. Bases de código de produção maiores frequentemente apresentam mais melhorias.

Comparação de Processos de Construção

Esta secção explica como o SPGO se integra no pipeline de construção, caso queiras compreender a mecânica.

Processo normal de construção

Numa compilação padrão de lançamento em C/C++:

  • Entradas: Ficheiros de código-fonte (.cpp, .h) e flags do compilador em modo de lançamento (/O2, /GL, e assim sucessivamente).
  • Processo: O compilador aplica otimizações padrão, como heurísticas de expansão em linha, premissas de predição de desvios e decisões sobre a disposição do código baseadas apenas em análise estática. Não tem dados sobre como o programa realmente se comporta quando corre.
  • Saída: Executável (.exe), ficheiros DLL (.dll), informação de depuração (.pdb).

Diagrama do processo normal de compilação de lançamento mostrando ficheiros de código-fonte e exemplo de comutador do compilador /GL como entradas que fluem para uma etapa de compilação, que produz saídas .exe, .dlle .pdb.

Sem dados de execução, caminhos quentes e frios recebem tratamento semelhante.

Processo de compilação habilitado por SPGO

O SPGO adiciona dados de perfil como uma nova entrada ao pipeline de compilação:

  • Entradas: Código-fonte, o ficheiro de perfil .spd (contagens de amostras de uma execução de criação de perfil), flags do compilador em modo release, /link /spgo e /spdin:<path> para especificar um ficheiro SPD de entrada (se não for especificado, por predefinição é usado um ficheiro .spd com o nome do binário e localizado na pasta obj).
  • Processo: O linker lê o SPD juntamente com o código intermédio. Utiliza dados de frequência de ramificações para tomar melhores decisões de inlining, layout de código e de ordenação das ramificações. As funções quentes são organizadas para acesso rápido; o código frio é deslocado para fora do caminho crítico.
  • Saída: Executável otimizado (.exe), ficheiros DLL otimizados (.dll), informação de depuração (.pdb), e um novo ficheiro .spd para futuras iterações de criação de perfil.

Diagrama do processo de compilação com SPGO, que mostra o código-fonte e os ficheiros de dados de perfil (.spd) como entradas para a etapa de compilação, com a opção adicional de linker /spgo. O processo de compilação gera ficheiros .exe otimizados, .dll, informações de depuração (.pdb) e novos ficheiros de dados de perfil (.spd).

A perceção-chave: o SPGO transfere decisões de otimização das heurísticas do compilador e do linker para escolhas baseadas em dados baseadas na execução real.

Bandeiras-chave

Flag Tipo Purpose
/spgo Linker Ativa o SPGO. Incorpora metadados SPGO no binário e cria um ficheiro de saída vazio .spd , a menos que /spdin seja especificado, caso em que o ficheiro especificado .spd é usado como entrada.
/spdin:<path> Linker SPD de entrada - fornece dados de perfil ao ligador para otimização
/spd:<path> Linker Caminho de saída do SPD - especifica onde o novo SPD é gravado (opcional; por predefinição, no mesmo diretório que o binário). Serve como caminho SPD de entrada se /spdin não for especificado.
/GL Compilador Otimização para todo o programa - necessária para que o SPGO funcione entre unidades de tradução
/O1, /O2 (Minimizar o Tamanho, Maximizar a Velocidade) Compilador Otimizar para velocidade; permite otimizações agressivas que o SPGO pode melhorar

Como o SPGO difere do PGO

O PGO (Profile-Guided Optimization) exige que compile o binário com opções de instrumentação (/GENPROFILE), execute o binário instrumentado, mais lento, para recolher os ficheiros .pgc de contagem de execução e, em seguida, refaça a ligação com /USEPROFILE. O compilador obtém contagens exatas de execução, mas primeiro tens de instrumentar o código. Para mais informações sobre este processo, consulte Otimizações guiadas por perfis.

O SPGO utiliza contadores de desempenho de CPU por hardware para recolher amostras estatísticas do seu binário de lançamento não instrumentado. Executa o teu binário existente, perfila-o usando xperf, converte o traço num ficheiro SPD e reconstrói. Não há construção instrumentada nem lentidão durante o perfilamento. O compilador recebe dados de amostragem estatística em vez de contagens exatas, que são menos precisas mas mais fáceis de obter e não requerem alterações de código. Permite também a análise de componentes do sistema ou componentes em tempo real que são difíceis de recolher dados com uma abordagem instrumentada. Também podes perfilar binários finais/de envio.

Este tutorial cobre três métodos de perfilagem: LBR, PMC e temporizador do sistema operativo. Escolhe o seu método em Escolha o seu método de perfil. Para uma comparação detalhada do processo de compilação normal versus o processo de compilação SPGO, incluindo uma tabela de referência de flags, veja Comparação de Processos de Construção.

Configurar perfcore.ini

⚠️ Obrigatório: Sem este passo, xperf não fornece os dados necessários de perfil. Complete este passo antes de correr xperf.

O Windows Performance Toolkit (WPT) utiliza perfcore.ini, que se encontra, caso tenha instalado o WPT na localização predefinida, em C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\perfcore.ini, para registar os fornecedores de DLL de que necessita para o SPGO.

Abra o Bloco de Notas do Windows como administrador. Depois abre perfcore.ini. Encontre a secção da lista DLL e adicione as seguintes entradas, uma por linha:

perf_spt.dll
perf_lbr.dll

Se xperf.exe não estiver instalado, veja Questões gerais para instalar.

Salve e feche perfcore.ini. Os ficheiros DLL já vêm no mesmo diretório xperf.exe , por isso não precisas de os copiar para lado nenhum. Só os está a registar em perfcore.ini. Certifica-te de que xperf está na tua PATH.

Criar o aplicativo de exemplo

A aplicação de exemplo deste tutorial é um programa em C++ que lê texto a partir de entrada padrão e produz a contagem de linhas, contagem de palavras, contagem total de caracteres, uma tabela de frequência de caracteres e o tempo decorrido para processar o ficheiro em milissegundos. Está escrito em C++, mas o SPGO também funciona com C. O fluxo de trabalho é idêntico para projetos C.

Crie um ficheiro nomeado textCount.cpp no seu diretório de trabalho e adicione o seguinte código-fonte:

// 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;
}

Compile e execute o exemplo para estabelecer um valor de referência

Antes de aplicar o SPGO, constrói textCount e executa-o contra um ficheiro de texto grande, como Guerra e Paz (podes descarregá-lo do Project Gutenberg), para veres a que velocidade corre. Este passo mostra-lhe o desempenho antes de o otimizar usando o SPGO:

Compilação:

cl /EHsc /GL /O2 textCount.cpp

Executar:

textCount.exe < warAndPeace.txt

Vê uma saída semelhante a:

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

=== CHARACTER FREQUENCIES ===

Letters:
a: 202719
...

Elapsed time: 512.000 ms

Registe o valor Elapsed time. Vai compará-lo com o tempo otimizado com SPGO em Meça os resultados.

Construir textCount com /spgo

Agora compile o textCount com SPGO ativado. Este passo lança as bases para recolher dados de perfilagem.

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

Quando a construção termina, vê uma mensagem como:

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

Esta mensagem aparece na primeira /spgo versão. O linker cria o ficheiro SPD mas ele continua vazio, por isso ainda não aplica otimizações SPGO. Depois de executar o binário, recolher dados de perfil e convertê-los para SPD, não verá esta mensagem.

Explicações sobre a bandeira:

Flag Purpose
/EHsc Ativar o tratamento de exceções em C++
/GL Otimização para todo o programa — necessária para o SPGO. Adia a otimização final para o tempo de ligação, permitindo inlining entre módulos, layout de código e decisões de eliminação de código morto.
/O2 Otimizar para velocidade — permite expansão em linha agressiva, otimização de ciclos, remoção de código morto e transformações relacionadas.
/link /debug Passe /debug ao linker para gerar informação de depuração (.pdb), que o xperf usa para mapear amostras de perfil ao código-fonte.
/spgo Opção do linker SPGO — incorpora metadados SPGO no binário e cria um ficheiro vazio textCount.spd junto do executável.

Note

/spgo é uma bandeira de ligação. Passe-o para o ligador através de /link /spgo no comando cl.

A /spgo flag ainda não otimiza o binário. Prepara-o para perfilamento. A otimização ocorre no Rebuild textCount com /spdin depois de o SPD ser preenchido com dados reais de execução.

Note

Para escrever o SPD numa localização específica, adicione a bandeira opcional /spd:<path> do linker. Por exemplo: /link /debug /spgo /spd:.\profiles\textCount.spd. Se omitir esta opção, o SPD é criado em conjunto com o .exe.

Escolha o seu método de perfilagem

O SPGO suporta três métodos de perfilagem. O método que usas depende do teu hardware.

Os três métodos de perfilação

Método Qualidade da amostra Requisitos de hardware Melhor para
LBR (registo do último desvio) Mais alto — regista sequências de ramificações recentemente tomadas, fornecendo ao otimizador dados ricos de fluxo de controlo por amostra Intel Haswell (2013) ou posterior; AMD Zen 4 (2022) ou posterior; ARM64 ARMv9.2-A (2020) ou posterior A maioria do hardware moderno para computadores de secretária
Modo PMC/IP (Contador de monitorização de desempenho/modo do ponteiro de instrução) Bom. Captura amostras de apontadores de instruções com pilhas de chamadas usando a Unidade de Monitorização de Desempenho (PMU) da CPU, recolhida através do Event Tracing for Windows (ETW) Qualquer CPU x64 ou ARM64 com PMU Hardware sem suporte LBR
Temporizador do OS Básico — amostras baseadas em temporizador Qualquer CPU x64 ou ARM64, máquinas virtuais sem passagem direta da PMU VMs e hardware mais antigo

Com o modo PMC / IP, cada interrupção de hardware dá-te apenas um ponto de dados: "a CPU estava no endereço 0x1A2B3C4D quando a interrupção foi disparada". Com o LBR, cada interrupção dá-te uma pilha dos últimos 16–32 ramos que a CPU fez antes da interrupção disparar. O otimizador obtém melhores dados de fluxo de controlo e pode tomar melhores decisões de inlining e layout.

Detecta o teu caminho

Execute os dois comandos seguintes para determinar qual o caminho de perfil que a sua máquina suporta. Estes comandos não requerem um prompt elevado.

Passo 1: Verifique se há suporte para LBR. Este teste funciona em Intel/AMD/ARM64.

Execute o seguinte a partir de um prompt de comandos de programador 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 este comando encontrar uma linha contendo LBR, TimeStamp, então a sua máquina suporta LBR. Utilize o caminho LBR.
  • Caso contrário, continue para o Passo 2.

Passo 2: Verifique se há suporte PMC (sem LBR)

xperf.exe -pmcsources | findstr TotalIssues
  • Se este comando produzir saída, então a tua máquina suporta contadores PMC mas não LBR. Use o caminho do PMC.
  • Se este comando não produzir saída, então use o caminho do temporizador do sistema operativo.

Para mais informações sobre a recolha de eventos PMU com xperf, consulte Gravação de eventos PMU hardware com xperf.

Tabela de decisão

LBR, TimeStamp saída TotalIssues saída O teu caminho
Não vazio (não verificado) LBR
Vazio Não vazio PMC
Vazio Vazio Temporizador do OS
Processador ARM64 N/A PMC (se PMU disponível) ou temporizador do sistema operativo

Escolha a sua abordagem

Decida se deve utilizar o caminho LBR, PMC ou do temporizador do SO com base nos resultados da deteção. Cada caminho tem diferentes xperf parâmetros iniciais para recolher os dados de perfil apropriados. Segue o caminho que corresponde às capacidades do teu hardware.

O teu percurso:

Todos os percursos convergem novamente em Executar a carga de trabalho e parar o xperf.

Os comandos nesta secção dependem do caminho de perfilagem que identificou em Escolha o seu método de perfil. Localize a subsecção que corresponde ao seu caminho, execute o comando xperf start e depois avance para Executar a carga de trabalho e parar o xperf para executar a carga de trabalho e parar o xperf.

⚠️ Executar como Administrador:xperf requer uma linha de comandos de programador elevada (Administrador). Sem elevação, xperf retorna "failed to configure counters".

Caminho LBR

Comece xperf pela coleção LBR:

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

Explicação dos parâmetros:

Parâmetro Purpose
LOADER+PROC_THREAD+PMC_PROFILE Fornecedores do kernel: eventos do carregador (mapeamento de módulos), eventos de processo/thread (contexto de execução) e eventos de criação de perfis PMC
-MinBuffers 4096 -MaxBuffers 4096 -BufferSize 4096 Buffers circulares de grande dimensão para evitar a perda de amostras durante uma execução completa de Guerra e Paz
-pmcprofile BranchInstructionRetired Acionador de evento PMC: gerar uma amostra em cada N-ésima instrução de desvio concluída
-LastBranch PmcInterrupt Ativa gravação por hardware LBR: em cada interrupção PMC, captura a pilha de registos de última ramificação de hardware
-setProfInt BranchInstructionRetired 16384 Intervalo de exemplo: dispare uma interrupção a cada 16.384 instruções de desvio retiradas

Depois de iniciar o xperf, continue a executar a carga de trabalho e pare o xperf.

Caminho PMC (sem LBR)

Inicie xperf com a recolha no modo PMC/IP:

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

Explicação dos parâmetros:

Parâmetro Purpose
LOADER+PROC_THREAD+PMC_PROFILE+PROFILE Adiciona PROFILE (amostragem da CPU) e PMC_PROFILE para eventos PMC; não -LastBranch
-pmcprofile InstructionRetired Disparador de evento PMC: exemplo em instruções retiradas (modo ponteiro de instrução)
-setProfInt InstructionRetired 16384 Dispara uma interrupção a cada 16.384 instruções retiradas
-stackwalk profile Capturar uma pilha de chamadas em cada interrupção de perfil, fornecendo dados da cadeia de chamadas em vez de sequências de ramificações

Comparado com LBR: sem -LastBranch bandeira; usa InstructionRetired em vez de BranchInstructionRetired. O resultado são amostras de ponteiros de instrução com pilhas de chamadas, não sequências de ramificações. Este caminho ainda fornece dados eficazes para o otimizador, mas é ligeiramente menos rico.

Depois de iniciar xperf, continue a executar a carga de trabalho e parar o xperf.

Caminho do temporizador do SO

Inicie o xperf com amostragem baseada em temporizador do sistema operativo:

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

Explicação dos parâmetros:

Parâmetro Purpose
LOADER+PROC_THREAD+PROFILE Sem eventos PMC; Amostragem de CPU apenas através de interrupção do temporizador do sistema operativo
-setProfInt Timer 1221 Acionar na interrupção do temporizador do sistema operativo a cada 1 221 tiques do temporizador (aproximadamente 1 kHz)
-stackwalk profile Capturar uma pilha de chamadas em cada interrupção do temporizador

Comparado com LBR e PMC, este método não utiliza contadores de desempenho de hardware. O temporizador do sistema operativo dispara em intervalos de tempo aproximadamente fixos, independentemente da atividade da CPU. As amostras estão menos densamente correlacionadas com o código quente, mas ainda assim fornecem dados úteis de controlo e fluxo para o otimizador.

Execute a carga de trabalho e pare o xperf (todos os caminhos)

Com xperf em execução, execute textCount com Guerra e Paz:

textCount.exe < warAndPeace.txt

Depois de textCount terminar, pare xperf e escreva o ficheiro de rastreamento. Deixar que outros processos corram durante o perfil dilui a qualidade da amostra. Para melhores resultados, feche aplicações desnecessárias antes de executar a carga de trabalho.

xperf -stop -d textCount.etl

Depois de parar xperf (pode demorar algum tempo a gravar o ficheiro ETL), confirme que textCount.etl foi criado no diretório atual.

Converter o ficheiro ETL para SPT

Este passo é o mesmo para os três caminhos de perfilagem.

Execute SPTAggregate.exe para processar o rastreio ETL bruto e crie um ficheiro de perfil SPT:

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

Explicação dos parâmetros:

Parâmetro Purpose
/binary textCount.exe O binário do qual extrair amostras. O ETL pode conter amostras de todos os processos que foram executados durante o perfilamento
/etl textCount.etl Ficheiro de traço ETL de entrada
textCount.spt Ficheiro de perfil SPT de saída

SPTAggregate Produz um resumo que mostra quantas amostras recolheu. Este resumo é a sua primeira confirmação de que a definição de perfis funcionou.

Verifica o resultado de SPTAggregate com o caminho que seguiste:

  • Caminho LBR: Procure um número de Amostras LBR usadas diferente de zero.
  • Caminho PMC: Procura uma contagem de PMC ou stack não nula.
  • Caminho do temporizador do sistema operativo: Procura uma contagem de amostras de stack usadas diferente de zero.

Se todas as contagens forem zero, consulte Resolução de Problemas antes de continuar.

Converter o ficheiro SPT para SPD

O teu percurso:

Tanto os caminhos do temporizador PMC como OS usam /mode:IP porque ambos produzem amostras de apontadores de instrução.

O passo seguinte divide-se consoante o caminho de criação de perfil, especificamente com base na opção /mode passada para SPDConvert.exe.

Modo LBR

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

/mode:LBR indica SPDConvert para interpretar o SPT como contendo dados de sequência de ramificação LBR.

Modo IP (PMC e temporizador do sistema operativo)

Tanto o PMC como o temporizador OS produzem amostras de apontadores de instruções, pelo que ambos usam o mesmo comando de conversão:

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

/mode:IP indica SPDConvert para interpretar o SPT como contendo amostras de apontadores de instrução.

Advertência

Usar o modo errado para o seu tipo de dados pode produzir um SPD vazio ou malformado. Se fizeste um perfil com LBR, usa /mode:LBR. Se efetuaste a criação de perfil com PMC ou o temporizador do sistema operativo, usa /mode:IP. A SPTAggregate saída resumida do Convert the ETL file to SPT mostra quais os tipos de amostras recolhidos e confirma o modo correto a utilizar.

Depois de executar SPDConvert, confirme que foi textCount.spd criado (ou atualizado) no diretório atual.

Interpretar a saída do SPDConvert

O comando SPDConvert textCount.spd textCount.spt imprime um resumo de cobertura de blocos antes e depois, por exemplo:

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

Este resumo mostra a percentagem dos blocos de código binários que têm dados de perfil associados. Uma percentagem maior é melhor. Cobertura acima de 70% é excelente, enquanto cobertura abaixo de 40% pode limitar a eficácia da otimização. Se a cobertura for baixa, execute a carga de trabalho de perfilagem por mais tempo ou combine múltiplos ficheiros SPT de execuções separadas com cargas de trabalho diferentes. Por exemplo, podes correr textCount contra vários ficheiros de texto para exercer diferentes caminhos de código.

Poderá ver um aviso de SPDConvert como o seguinte:

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.

Este aviso significa que a sua execução de criação de perfil não recolheu amostras suficientes para o otimizador conseguir otimizar, com confiança, todas as funções mais utilizadas. O SPD continua a ser utilizável, mas pode melhorar os resultados fazendo o seguinte:

  • Fazer a carga de trabalho mais longa (por exemplo, 5 ou mais minutos em vez de 1 minuto) ou usar cargas de trabalho diferentes.
  • Reduzir o valor de -setProfInt no comando xperf para aumentar a taxa de amostragem. A desvantagem é que esta alteração produz um ficheiro ETL maior, que demora mais a processar.
  • A combinação de vários ficheiros SPT a partir de perfils separados executa-se passando-os todos para SPDConvert.

O ficheiro SPT é um formato binário. Para inspecionar o seu conteúdo, pode executar SPTDump.exe textCount.spt. Da mesma forma, PTDump.exe textCount.spt mostra os dados do perfil compilado depois de executar SPDConvert. Ambas as ferramentas são úteis para verificar amostras não nulas antes de avançar.

Recriar a contagem de texto com /spdin

Reconstrua textCount usando o ficheiro SPD preenchido. O linker lê os dados do perfil e aplica otimizações SPGO.

Este passo é o mesmo para os três caminhos de perfilagem.

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

Nova opção (comparada com Build textCount with /spgo):

Flag Purpose
/spdin:textCount.spd Forneça os dados do perfil SPD ao linker para otimização

O comando ainda inclui /spgo. Gera um novo ficheiro SPD juntamente com o binário otimizado, que pode usar como ponto de partida para iterações subsequentes de perfil.

Advertência

O ficheiro SPD está associado ao binário exato contra o qual faz perfil. Se reconstruir textCount sem /spdin, ou reconstruir a partir da fonte alterada, tem de gerar um novo ficheiro SPD. O atual não corresponde ao GUID do novo binário, e o linker não o usa.

Após a reconstrução com /spdin, o linker gera estatísticas sobre quanto do seu código foi otimizado usando dados de perfil. Por exemplo:

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

Uma percentagem alta significa que o SPD cobre bem o seu binário. Se a percentagem for baixa (por exemplo, abaixo de 90%), ou a carga de trabalho de criação de perfil não abrangeu uma parte suficiente do binário, ou o binário mudou significativamente desde que o perfil foi obtido. Em ambos os casos, refaça a criação de perfil com base no binário atual.

O que a SPGO faz com os dados do seu perfil

O SPGO utiliza os dados amostrais recolhidos para preencher os valores de contagem em cada bloco e em cada aresta no grafo de fluxo de controlo do programa. Estas contagens conduzem otimizações como:

  • Expansão inline orientada por perfis: Faz a expansão inline agressiva de locais de chamada frequentes, evitando o aumento excessivo do código causado pela expansão inline de caminhos pouco frequentes.
  • Separação de código quente/frio: Mover código raramente executado para secções separadas do binário, melhorando a utilização do cache de instruções e o comportamento de paginação.
  • Disposição das funções: Coloque funções que se invocam frequentemente próximas entre si no binário, reduzindo falhas de página e melhorando a localidade. As funções otimizadas estão organizadas em grupos COFF de alta afinidade no binário.
  • Decisões de tamanho/velocidade: Compilar funções de fase para velocidade e funções de frio para tamanho. Rotinas sem impactos de perfil observados podem ser compiladas para tamanho em vez de velocidade, limitando otimizações como inlining e desenrolamento de loops nesses caminhos frios.
  • Desvirtualização especulativa: Quando a amostragem revela que uma chamada indireta visa consistentemente a mesma função, o SPGO pode especular sobre esse alvo e inlineá-lo, com um recurso de reserva para o caso incomum.

Meça os resultados

Execute textCount novamente e compare os tempos decorridos.

textCount.exe < warAndPeace.txt

Recolher várias execuções para cada configuração e usar a mediana. Uma única execução não é fiável porque o agendamento do sistema operativo e o ruído do sistema podem distorcer as medições individuais.

Criar Tempo representativo decorrido
Linha de base (cl /EHsc /O2) (a tua medida)
/spgo compilação (ainda sem dados de criação de perfil) (deve estar perto da linha de base)
Otimizado para SPGO (/spdin) (deve mostrar melhoria)

Num teste, o SPGO usando o método LBR proporcionou aproximadamente 7% de redução no tempo decorrido. Os seus resultados podem variar consoante os seus projetos, porque os ganhos do SPGO dependem do grau em que a carga de trabalho de criação de perfil representa a execução típica. Bases de código maiores, preenchidas com ramificações, tendem a apresentar melhorias maiores na faixa de 5–10%. O método de perfilagem afeta a qualidade da otimização. O LBR normalmente produz melhores resultados do que o PMC, que produz melhores resultados do que o temporizador do sistema operativo. Se estiveres na via do temporizador do sistema operativo, espera ganhos mais reduzidos.

O caminho LBR seguido neste tutorial foi aplicado ao projeto SQLite , que é uma biblioteca de base de dados de produção. O binário SQLite otimizado para SPGO apresentou uma melhoria de aproximadamente 7%.

Aplique SPGO ao seu próprio projeto

Use esta lista de verificação para aplicar SPGO à sua própria aplicação em C ou C++.

  1. Adicione /link /spgo ao seu comando de compilação de lançamento existente. Modifica o teu script de build ou ficheiro de projeto:

    cl /EHsc /GL /O2 myapp.cpp /link /spgo
    
  2. Escolha uma carga de trabalho representativa. Selecione um cenário de utilização real que percorra os fluxos de execução mais frequentes da sua aplicação. Utilize dados semelhantes aos de produção. Evite o seguinte como principal carga de trabalho de perfilamento: testes de cobertura de código (que não pressionam os gargalos de desempenho), caminhos de erro incomuns, fases de arranque e desligamento, e caminhos de código obsoletos. Esta carga de trabalho gere o perfil que alimenta o otimizador.

  3. Executa o xperf utilizando o caminho identificado. Use o caminho que identificou em Escolha o seu método de perfil (LBR, PMC ou temporizador do sistema operativo). Iniciar xperf, executar a carga de trabalho uma vez, parar xperf, e capturar o ficheiro ETL.

  4. Para o caminho do temporizador PMC ou do sistema operativo, execute o SPTAggregate e o SPDConvert com a opção /mode correta. Converta ETL em SPT e, em seguida, em SPD. Utilize /mode:LBR para dados LBR; utilize /mode:IP para dados PMC ou do temporizador do SO.

  5. Reconstruir com /spdin:<your-spd-path>. Compile a sua aplicação com o SPD preenchido:

    cl /EHsc /GL /O2 yourApp.cpp /link /spgo /spdin:yourApp.spd
    
  6. Medir antes e depois. Execute a sua carga de trabalho tanto com binários não otimizados como com binários otimizados com SPGO. Recolha a mediana de várias execuções para cada configuração. Uma única execução não é fiável para avaliação de desempenho.

  7. Guarda o .spd ficheiro no controlo de versão. Adiciona o ficheiro .spd ao teu sistema de controlo de versões, juntamente com o teu código-fonte.

  8. Ativar o SPGO nas versões de lançamento do programador. Faça com que as compilações de Release da sua equipa utilizem os mesmos binários otimizados com SPGO que os utilizados em produção. Isto ajuda a detetar regressões de desempenho cedo.

  9. Desativar o SPGO nas compilações de depuração.

  10. Observe as estatísticas de completude do perfil do linker. Após cada build com /spgo, note a percentagem de funções perfiladas otimizadas usando dados de perfil. Se este valor cair significativamente (abaixo dos 90%), reprofila o binário atual. As alterações no código acumulam-se e o SPD pode tornar-se obsoleto.

Alternativa à utilização xperf

Outra forma de recolher dados de perfil é usar um perfilador de amostragem como o Windows Performance Recorder (WPR). O WPR está instalado por defeito no Windows 10 e versões posteriores. Recolhe dados semelhantes a xperf. Pode configurar o WPR para recolher amostras de CPU com pilhas de chamadas e, em seguida, exportar os dados para um ficheiro ETL que pode processar com SPTAggregate e SPDConvert, tal como o ETL xperf. Aqui está um exemplo de utilização do WPR para recolher dados de perfis:

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

Para mais informações sobre o uso do WPR, veja Using Windows Performance Recorder.

Distribuição do SPD

É possível:

  • Verifica o .spd ficheiro diretamente no controlo de versões juntamente com o teu código-fonte.
  • Partilhe o ficheiro .spd com os colegas de equipa para que possam compilar com otimizações SPGO sem terem de criar novamente o perfil.
  • Empacota o .spd ficheiro com os teus binários como um artefacto versionado (por exemplo, um pacote NuGet) e regista qual versão corresponde a que binário.
  • Regenere o .spd ficheiro a qualquer momento repetindo o fluxo de trabalho de perfilagem.

O SPD está ligado ao binário exato a partir do qual foi construído. Após alterações significativas no código, reprofila para gerar um SPD novo. Durante a /spdin compilação, o compilador também produz um novo .spd ficheiro. Guarda este novo SPD como artefacto de compilação — é o ponto de partida para a tua próxima iteração de criação de perfil.

Reutilizar informação SPD entre compilações

O conceito de "carry forward" no SPGO permite adicionar dados de perfil a um ficheiro SPD existente sem ter de projetar todos os seus cenários novamente do zero e sem perder informações de perfil existentes. Também podes ajustar quanto peso dar a dados de perfis mais antigos. Esta flexibilidade é útil quando pode haver alterações no comportamento ao longo do tempo e não se pretende perder completamente a informação de perfil recolhida nas execuções anteriores dos cenários. Por exemplo, uma DLL pode ver diferentes APIs chamadas à medida que a aplicação que a chama evolui. Ainda queres as otimizações baseadas na forma como antes se comportava, mas também queres combinar oportunidades de otimização para quando agora, por vezes, se comporta de forma diferente. Podes evoluir o perfil ao longo do tempo misturando dados antigos e novos.

Quando executas SPDConvert com um novo ficheiro SPT, passa o nome do ficheiro SPD existente. Depois, use a opção /retire:N para controlar até que ponto SPDConvert atribui menos importância aos dados de perfil antigos quando adiciona novos ficheiros SPT:

  • O padrão (/retire:8) dá mais peso aos dados mais recentes.
  • Use /retire:0 para dar peso igual a todas as corridas.
  • Uso /retire:16 para deixar que apenas os dados mais recentes contem.

Troubleshooting

Encontre o seu problema:

Problemas do caminho LBR

Problema Causa provável Correção
Zero amostras de LBR na saída SPTAggregate O CPU não suporta LBR, ou a VM não expõe LBR Execute o comando de deteção a partir de Detete o seu caminho. Se estiveres numa VM Hyper-V, executa Set-VMProcessor MyVMName -Perfmon @("pmu", "lbr") no host. Se o LBR não estiver disponível, mude para o caminho do temporizador PMC ou do sistema operativo.
O processador suporta LBR mas SPTAggregate mostra 0 amostras LBR perfcore.ini Registo de DLL incompleto Conclua a configuração perfcore.ini em Configurar perfcore.ini. Certifique-se de que perf_lbr.dll está registado.
SPDConvert falha ou produz um SPD vazio Flag errado /mode , ou SPT contém apenas amostras em modo IP Confirmo SPTAggregate que a saída mostrou amostras LBR. Se a saída mostrar apenas amostras em modo IP, mude para /mode:IP.

Problemas com o percurso da PMC

Problema Causa provável Correção
Nenhuma amostra PMC na saída SPTAggregate perfcore.ini Registo de DLL incorreto Conclua a configuração perfcore.ini em Configurar perfcore.ini. Certifique-se perf_spt.dll de que está registado. Sem esta DLL, xperf não produz quaisquer amostras PMC e não apresenta nenhuma mensagem de erro.
Execute xperf.exe -pmcsources para ver a lista de origens dos contadores de desempenho disponíveis no seu CPU. Se não vir entradas como SPT_OP_RETIRE_INSTR ou SPT_OP_RETIRE_BR_INSTR ou SPT_OP_ETW_INSTR, então o registo da DLL em perfcore.ini pode estar incompleto ou o CPU pode não suportar PMC. Se não conseguires resolver o problema de registo da DLL, tenta, em vez disso, a via do temporizador do sistema operativo.
findstr InstructionRetired devolve a saída mas xperf não produz amostras Mascaramento dos contadores PMC da VM Verifica se está a correr numa VM. Ativa a PMU em Hyper-V com Set-VMProcessor, ou muda para o caminho do temporizador do sistema operativo.
SPDConvert falha no caminho PMC Utilizar /mode:LBR num SPT apenas com IP Mude para /mode:IP.

Problemas com o caminho do temporizador do sistema operativo

Problema Causa provável Correção
Menos melhoria do que o esperado Esperado - O temporizador do sistema operativo tem menor fidelidade Isto é normal. O otimizador tem menos informação sobre o fluxo de ramificações a partir de amostras do temporizador do que de LBR ou PMC. Os ganhos de desempenho são menores. Considere atualizar para PMC ou LBR se o hardware o permitir.
Amostras com temporizador zero xperf não foi executado numa linha de comandos com privilégios elevados, ou o fornecedor de PROFILE está em falta Confirme que está a executar com privilégios de Administrador. Confirme que -stackwalk profile foi fornecido ao comando xperf.

Questões gerais (todos os caminhos)

Problema Causa provável Correção
"failed to configure counters" erro xperf não está a ser executado como administrador Reiniciar o prompt de comandos como Administrador (clique > com o botão direito em Executar como administrador). O XPERF requer privilégios elevados para configurar contadores de desempenho de hardware.
xperf não encontrado xperf.exe não está no PATH Confirma que o ADK do Windows está instalado. Consulte C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\. Adiciona esse diretório ao teu PATH, ou executa o xperf diretamente a partir dele.
textCount.etl não foi criado O xperf falhou silenciosamente Confirme que está a executar o programa como administrador. Reexecute o comando xperf start e verifique se há erro.
SPTAggregate falha com "binário não encontrado" textCount.exe não está no diretório atual ou o caminho está incorreto Confirma que estás no mesmo diretório que textCount.exe, ou fornece o caminho completo para o /binary parâmetro.
Ficheiro SPD não criado SPDConvert Falhou Verifica se o textCount.spt tamanho é diferente de zero. Executa SPTDump.exe textCount.spt para inspecionar o seu conteúdo.
/spdin A compilação não traz melhorias Incompatibilidade de GUID/idade entre SPD e binário O SPD foi construído a partir de um textCount.exe diferente. Perfilar novamente a versão atual para gerar um SPD novo.
Erro de versão MSVC em /spgo Conjunto de ferramentas MSVC anterior à v14.51 Abra o Instalador Visual Studio >Componentes Individuais> instale MSVC v14.51 ou posterior. Reabra o prompt de comandos do programador.

Passos seguintes

Depois de completar este tutorial, explore estas capacidades para obter mais do SPGO:

  • Fusão de perfis: Executar múltiplas cargas de trabalho, acumular ficheiros SPT de cada execução e passar todos para SPDConvert. Um SPD misto reflete toda a gama de padrões reais de utilização e produz melhores otimizações do que um perfil de cenário único. Utilize a opção /retire:N para controlar até que ponto SPDConvert reduz a importância dos dados de perfil mais antigos quando adiciona novos ficheiros SPT. O padrão (/retire:8) dá mais peso aos dados mais recentes. Use /retire:0 para dar peso igual a todas as corridas; use /retire:16 para que apenas os dados mais recentes contem.
  • Os melhores resultados vêm da combinação de perfis de múltiplas fontes, como benchmarks que enfatizam cenários-chave e dados do mundo real (quando disponíveis). Passe ficheiros SPT de todas as fontes para SPDConvert. Repita um ficheiro SPT na lista de argumentos para lhe atribuir mais peso (por exemplo, SPDConvert myapp.spd critical.spt critical.spt common.spt atribui a critical.spt um peso duas vezes superior ao de common.spt).
  • Otimização iterativa: Cada reconstrução com /spdin produz um novo SPD. Pode repetir o ciclo de execução, criação de perfil e reconstrução. As iterações posteriores podem apresentar rendimentos decrescentes, mas uma segunda passada pode, por vezes, captar padrões que a primeira não detetou.
  • Alterações ao código: Após alterações significativas na fonte, recolha os dados do perfil. O SPD existente está ligado ao binário contra o qual foi perfilado. Não vai corresponder a um binário substancialmente reconstruído.
  • Frescura do perfil: O linker reporta a percentagem de funções perfiladas otimizadas usando dados de perfil após cada /spdin compilação. Se esta percentagem cair significativamente, é um sinal de que o código se afastou do perfil. Reprofila o binário atual.