Partilhar via


Solucionar problemas de impacto na integração de funções no tempo de compilação

Use a vista Funções do Build Insights para solucionar o impacto da inserção de funções no tempo de construção nos seus projetos de C++.

Pré-requisitos

  • Visual Studio 2022 17.8 ou superior.
  • O C++ Build Insights é habilitado por padrão se você instalar o desenvolvimento Desktop com carga de trabalho C++ ou o desenvolvimento de jogos com carga de trabalho C++.

Captura de ecrã do Instalador do Visual Studio com a tarefa 'Desenvolvimento para ambiente de trabalho com C++' selecionada.

A lista de componentes instalados é mostrada. O C++ Build Insights é realçado e selecionado, o que significa que está instalado.

Captura de tela do instalador do Visual Studio com a carga de trabalho de desenvolvimento de jogos com C++ selecionada.

A lista de componentes instalados é mostrada. O C++ Build Insights é realçado e selecionado, o que significa que está instalado.

Visão geral

O Build Insights, agora integrado ao Visual Studio, ajuda você a otimizar seus tempos de compilação, especialmente para grandes projetos, como jogos AAA. O Build Insights fornece análises, como a visualização Funções , que ajuda a diagnosticar a geração de código dispendiosa durante o tempo de compilação. Ele exibe o tempo necessário para gerar código para cada função e mostra o impacto do __forceinline.

A __forceinline diretiva indica ao compilador que deve embutir uma função, independentemente do seu tamanho ou complexidade. Inserir uma função pode melhorar o desempenho do tempo de execução, reduzindo a sobrecarga de chamar a função. A contrapartida é que ele pode aumentar o tamanho do binário e afetar seus tempos de compilação.

Para compilações otimizadas, o tempo gasto na geração de código contribui significativamente para o tempo total de compilação. Em geral, a otimização da função C++ acontece rapidamente. Em casos excecionais, algumas funções podem se tornar grandes o suficiente e complexas o suficiente para colocar pressão sobre o otimizador e visivelmente retardar suas construções.

Neste artigo, saiba como usar a visualização de Funções do Build Insights para encontrar gargalos internos na sua compilação.

Definir opções de compilação

Para medir os resultados do , __forceinline Release porque as compilações de depuração não estão embutidas, já que as compilações de depuração usam a __forceinline opção do /Ob0compilador, que desabilita essa otimização. Defina a compilação para Release e x64:

  1. No menu suspenso Configurações da Solução, escolha Publicação.
  2. No menu suspenso Plataformas de Solução , escolha x64.

Captura de ecrã do menu pendente Configuração da Solução definido como Lançamento e do menu pendente Plataforma da Solução definido como x64.

Defina o nível de otimização para otimizações máximas:

  1. No Gerenciador de Soluções, clique com o botão direito do mouse no nome do projeto e selecione Propriedades.

  2. Nas propriedades do projeto, navegue até C/C++>Optimization.

  3. Defina o menu suspenso Otimização para Otimização Máxima (Favorecer a Velocidade) (/O2).

    Captura de ecrã da caixa de diálogo das páginas de propriedades do projeto. As configurações estão abertas para Propriedades de Configuração > C/C++ > Otimização. A lista suspensa Otimização está definida como Otimização máxima (Preferir Velocidade) (/O2).

  4. Clique em OK para fechar a caixa de diálogo.

Executar Build Insights

Em um projeto de sua escolha e usando as opções de compilação Release definidas na seção anterior, execute o Build Insights escolhendo no menu principal Build>Run Build Insights on Selection>Rebuild. Você também pode clicar com o botão direito do mouse em um projeto no gerenciador de soluções e escolher Executar Build Insights>Rebuild. Escolha Reconstruir em vez de Construir para medir o tempo de compilação para todo o projeto e não apenas para os poucos arquivos que podem estar sujos agora.

Captura de ecrã do menu principal com a opção Run Build Insights on Selection > Rebuild selecionada.

Quando a compilação termina, um arquivo ETL (Event Trace Log) é aberto. Ele é salvo na pasta apontada pela variável de ambiente Windows TEMP . O nome gerado é baseado no tempo de coleta.

Vista de função

Na janela do arquivo ETL, escolha a guia Funções . Ele mostra as funções que foram compiladas e o tempo que levou para gerar o código para cada função. Se a quantidade de código gerada para uma função for insignificante, ela não aparecerá na lista para evitar a degradação do desempenho da coleta de eventos de compilação.

Captura de ecrã do ficheiro de vista Build Insights Functions.

Na coluna Nome da Função, executePhysicsCalculations() é realçado e marcado com um ícone de fogo.

A coluna Tempo [seg, %] mostra quanto tempo levou para compilar cada função em tempo de responsabilidade do relógio de parede (WCTR). Essa métrica distribui o tempo do relógio de parede entre as funções com base no uso de threads paralelos do compilador. Por exemplo, se dois threads diferentes estiverem compilando duas funções diferentes simultaneamente dentro de um período de um segundo, o WCTR de cada função será registrado como 0,5 segundos. Isso reflete a parte proporcional de cada função no tempo total de compilação, levando em consideração os recursos que cada uma consumiu durante a execução paralela. Assim, o WCTR fornece uma melhor medida do impacto que cada função tem no tempo geral de construção em ambientes onde várias atividades de compilação ocorrem simultaneamente.

A coluna Forceinline Size mostra aproximadamente quantas instruções foram geradas para a função. Clique na divisa antes do nome da função para ver as funções individuais embutidas que foram expandidas nessa função e aproximadamente quantas instruções foram geradas para cada uma.

Você pode classificar a lista clicando na coluna Tempo para ver quais funções estão levando mais tempo para compilar. Um ícone de "fogo" indica que o custo de geração dessa função é alto e vale a pena investigar. O uso excessivo de __forceinline funções pode retardar significativamente a compilação.

Você pode pesquisar uma função específica usando a caixa Funções de filtro . Se o tempo de geração de código de uma função for muito pequeno, ele não aparecerá na Visualização de funções .

Melhore o tempo de construção ajustando o inlining da função

Neste exemplo, a performPhysicsCalculations função está levando mais tempo para compilar.

Captura de ecrã da vista Funções do Build Insights.

Na coluna Nome da Função, executePhysicsCalculations() é realçado e marcado com um ícone de fogo.

Ao selecionar a divisa antes dessa função e, em seguida, classificar a coluna Forceinline Size da maior para a menor, vemos os maiores contribuintes para o problema.

Captura de ecrã da vista Funções do Build Insights com uma função expandida.

performPhysicsCalculations() é expandido e mostra uma longa lista de funções que estavam embutidas dentro dele. Há várias instâncias de funções como complexOperation(), recursiveHelper() e sin() mostradas. A coluna Forceinline Size mostra que complexOperation() é a maior função embutida em 315 instruções. recursiveHelper() tem 119 instruções. Sin() tem 75 instruções, mas há muito mais instâncias dele do que as outras funções.

Existem algumas funções embutidas maiores, como Vector2D<float>::complexOperation() e Vector2D<float>::recursiveHelper() que estão contribuindo para o problema. Mas há muitos mais exemplos (nem todos mostrados aqui) de Vector2D<float>::sin(float), Vector2D<float>::cos(float), Vector2D<float>::power(float,int), e Vector2D<float>::factorial(int). Quando você as soma, o número total de instruções geradas excede rapidamente as poucas funções maiores geradas.

Olhando para essas funções no código-fonte, vemos que o tempo de execução será gasto dentro de loops. Por exemplo, aqui está o código para factorial():

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

Talvez o custo total de chamar essa função seja insignificante em comparação com o custo da função em si. Tornar uma função embutida é mais benéfico quando o tempo que leva para chamar a função (passando argumentos para a pilha, saltando para a função, removendo argumentos de retorno da pilha e retornando da função) é aproximadamente comparável ao tempo que leva para executar a função, e quando a função é chamada frequentemente. Quando isso não acontece, pode haver benefícios cada vez menores ao integrá-lo diretamente. Podemos tentar retirar a diretiva __forceinline para ver se isso ajuda no tempo de compilação. O código para power, sin(), e cos() é semelhante na medida em que o código consiste em um loop que é executado muitas vezes. Podemos tentar retirar a __forceinline diretiva também dessas funções.

Executamos novamente o Build Insights no menu principal escolhendo Build>Run Build Insights on Selection>Rebuild. Você também pode clicar com o botão direito do mouse em um projeto no gerenciador de soluções e escolher Executar Build Insights>Rebuild. Escolhemos Rebuild em vez de Build para medir o tempo de compilação para todo o projeto, como antes, e não apenas para os poucos arquivos que podem estar sujos agora.

O tempo de compilação vai de 25,181 segundos para 13,376 segundos e a performPhysicsCalculations função não aparece mais na visualização Funções porque não contribui o suficiente para o tempo de compilação a ser contado.

Captura de tela do arquivo de cabeçalho vetorial 2D.

Na coluna Nome da Função, executePhysicsCalculations() é realçado e marcado com um ícone de fogo.

O tempo da Sessão de Diagnóstico é o tempo total necessário para fazer a compilação, além de qualquer sobrecarga para coletar os dados do Build Insights.

A próxima etapa seria traçar o perfil do aplicativo para ver se o desempenho do aplicativo é afetado negativamente pela alteração. Se for, podemos adicionar __forceinline seletivamente de volta conforme a necessidade.

Clique duas vezes, clique com o botão direito do mouse ou pressione Enter enquanto estiver em um arquivo na visualização Funções para abrir o código-fonte desse arquivo.

Captura de ecrã de um clique com o botão direito do rato num ficheiro na vista Funções. A opção de menu Ir para arquivo de origem é realçada.

Sugestões

  • Use Ficheiro>Guardar Como para salvar o ficheiro ETL num local mais permanente para manter um registo das informações de tempo de compilação. Em seguida, você pode compará-lo com compilações futuras para ver como suas alterações estão melhorando as coisas.
  • Se você fechar a janela do Build Insights, abra-a novamente localizando o <dateandtime>.etl arquivo em sua pasta temporária. A TEMP variável de ambiente do Windows fornece o caminho da pasta de arquivos temporários.
  • Para explorar os dados do Build Insights com o Windows Performance Analyzer (WPA), clique no botão Abrir no WPA no canto inferior direito da janela ETL.
  • Arraste colunas para alterar a ordem das colunas. Por exemplo, você pode preferir mover a coluna Hora para ser a primeira coluna. Você pode ocultar colunas clicando com o botão direito do mouse no cabeçalho da coluna e desmarcando as colunas que não deseja ver.
  • A vista Funções fornece uma caixa de filtro para localizar uma função em que está interessado. Ele faz correspondências parciais no nome que você fornece.
  • Se se esquecer de como interpretar o que a vista Funções está a tentar mostrar-lhe, passe o rato sobre o separador para ver uma dica de ferramenta que descreve a vista. Se passar o rato sobre o separador Funções, a dica de ferramenta diz: "Vista que mostra estatísticas para funções onde os nós filhos são funções forçadas a serem incorporadas."

Solução de problemas

  • Se a janela Build Insights não aparecer, faça uma reconstrução em vez de uma compilação. A janela Build Insights não aparece se nada realmente for compilado; o que pode ser o caso se nenhum arquivo foi alterado desde a última compilação.
  • Se a visualização Funções não mostrar nenhuma função, talvez você não esteja criando com as configurações de otimização corretas. Certifique-se de que você está criando Release com otimizações completas, conforme descrito em Definir opções de compilação. Além disso, se o tempo de geração de código de uma função for muito pequeno, ele não aparecerá na lista.

Ver também

Dicas e truques do Build Insights
Funções embutidas (C++)
Compilações C++ mais rápidas, simplificadas: uma nova métrica para o tempo
Vídeo Criar insights no Visual Studio - Pure Virtual C++ 2023
Solucionar problemas de impacto do arquivo de cabeçalho no tempo de compilação
Exibição de funções para criar insights no Visual Studio 2022 17.8
Tutorial: vcperf e Windows Performance Analyzer
Melhorando o tempo de geração de código com o C++ Build Insights