Tutorial: solucionar problemas da função embutida no tempo de compilação
Use o modo de exibição de Funções do Build Insights para solucionar o impacto da função embutida no tempo de compilação nos seus projetos do C++.
Pré-requisitos
- Visual Studio 2022 17.8 ou superior.
- O Build Insights do C++ será habilitado por padrão se você instalar o desenvolvimento de Área de Trabalho com a carga de trabalho do C++ ou o desenvolvimento de Jogos com a carga de trabalho do C++.
A lista de componentes instalados é mostrada. O Build Insights do C++ está realçado e selecionado, o que significa que está instalado.
A lista de componentes instalados é mostrada. O Build Insights do C++ está 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 os tempos de compilação, especialmente para projetos grandes, como jogos AAA. O Build Insights fornece análises como o modo de exibição de Funções, que ajuda a diagnosticar uma geração de código dispendiosa durante o tempo de compilação. Ele exibe o tempo necessário para gerar o código para cada função e mostra o impacto de __forceinline
.
A diretiva __forceinline
informa ao compilador para embutir uma função, independentemente de seu tamanho ou complexidade. Embutir uma função pode melhorar o desempenho do tempo de execução, reduzindo a sobrecarga de chamar a função. As implicações incluem a possibilidade de aumentar o tamanho do binário e afetar os tempos de compilação.
Para compilações otimizadas, o tempo gasto gerando o código contribui significativamente para o tempo total de compilação. Em geral, a otimização da função do C++ ocorre rapidamente. Em casos excepcionais, algumas funções podem se tornar grandes o suficiente e complexas o suficiente para pressionar o otimizador e diminuir consideravelmente suas compilações.
Neste artigo, saiba como usar o modo de exibição de Funções do Build Insights para encontrar gargalos embutidos na sua compilação.
Definir opções de build
Para medir os resultados de __forceinline
, use uma compilação de Versão porque as compilações de depuração não estão embutidos __forceinline
, pois as compilações de depuração usam o comutador do compilador /Ob0
, o que desabilita essa otimização. Defina a compilar para Versão e x64:
- Na lista suspensa Configurações da Solução, escolha Versão.
- Na lista suspensa Plataformas da Solução, escolha x64.
Defina o nível de otimização como otimizações máximas:
No Gerenciador de Soluções, clique com o botão direito do mouse no nome do projeto e selecione Propriedades.
Nas propriedades do projeto, navegue até C/C++>Optimização.
Defina a lista suspensa Otimização como Otimização Máxima (Favorecer a Velocidade) (
/O2
).Clique em OK para fechar o diálogo.
Executar o Build Insights
Em um projeto de sua escolha e usando as opções de compilação da Versão definidas na seção anterior, execute o Build Insights escolhendo no menu principal Compilar>Executar o Build Insights na Seleção>Recompilar. Você também pode clicar com o botão direito do mouse em um projeto no gerenciador de soluções e escolher Executar o Build Insights>Recompilar. Escolha Recompilar, em vez de Compilar, para medir o tempo de compilação de todo o projeto e não apenas para os poucos arquivos que podem estar sujos no momento.
Quando a compilação for concluída, um arquivo de ETL (Log de Rastreamento de Eventos) será aberto. Ele será salvo na pasta apontada pela variável de ambiente do Windows TEMP
. O nome gerado é baseado no tempo de coleta.
Exibição de função
Na janela do arquivo de ETL, escolha a guia Funções. Ela mostra as funções que foram compiladas e o tempo necessário 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 será exibida na lista para evitar prejudicar o desempenho da coleção de eventos de compilação.
Na coluna Nome da Função, performPhysicsCalculations() está realçado e marcado com um ícone de fogo.:::
A coluna Tempo [s, %] mostra quanto tempo levou para compilar cada função no WCTR (tempo de responsabilidade do relógio de parede). Essa métrica distribui o tempo do relógio de parede entre as funções com base no uso de threads do compilador paralelo. Por exemplo, se dois threads diferentes estiverem compilando duas funções diferentes simultaneamente em um período de um segundo, o WCTR de cada função será registrado como 0,5 segundos. Isso reflete a participação proporcional de cada função do tempo total de compilação, levando em conta os recursos que cada um consumiu durante a execução paralela. Assim, o WCTR fornece uma melhor medida do impacto que cada função tem no tempo geral de compilação nos ambientes em que várias atividades de compilação ocorrem simultaneamente.
A coluna Tamanho de Forceinline 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 embutidas individuais que foram expandidas nessa função como 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 tendo 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 funções __forceinline
pode reduzir significativamente a compilação.
Você pode pesquisar uma função específica usando a caixa Filtrar Funções. Se o tempo de geração de código de uma função for muito pequeno, ele não será exibido no Modo de Exibição de Funções.
Melhorar o tempo de compilação ajustando a função embutida
Neste exemplo, a função performPhysicsCalculations
está levando mais tempo para ser compilada.
Na coluna Nome da Função, performPhysicsCalculations() está realçado e marcado com um ícone de fogo.
Investigando mais, selecionando a divisa antes dessa função e, em seguida, classificando a coluna Tamanho de Forceinline da mais alta para a mais baixa, vemos os maiores colaboradores para o problema.
performPhysicsCalculations() é expandido e mostra uma longa lista de funções que foram embutidas dentro dele. Há várias instâncias de funções, como complexOperation(), recursiveHelper() e sin(). A coluna Tamanho de Forceinline 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.
Há algumas funções embutidas maiores, como Vector2D<float>::complexOperation()
e Vector2D<float>::recursiveHelper()
que estão contribuindo para o problema. Mas há muito mais instâncias (nem todas mostradas aqui) de Vector2d<float>::sin(float)
, Vector2d<float>::cos(float)
, Vector2D<float>::power(float,int)
e Vector2D<float>::factorial(int)
. Quando você as adiciona, o número total de instruções geradas excede rapidamente as poucas funções geradas maiores.
Examinando essas funções no código-fonte, vemos que o tempo de execução será gasto dentro de loops. Por exemplo, este é 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 geral de chamar essa função seja insignificante em comparação com o custo da função em si. Tornar uma função embutida é mais útil quando o tempo necessário para chamar a função (efetuando push de argumentos na pilha, indo para a função, lançando argumentos de retorno e retornando da função) é aproximadamente semelhante ao tempo necessário para executar a função e quando a função é muito chamada. Quando esse não é o caso, pode haver retornos decrescentes ao torná-la embutida. Podemos tentar remover a diretiva __forceinline
dela para ver se isso ajuda no tempo de compilação. O código para power
, sin()
e cos()
é semelhante pois o código consiste em um loop que será executado muitas vezes. Podemos tentar remover a diretiva __forceinline
dessas funções também.
Executamos novamente o Build Insights no menu principal escolhendo Compilar>Executar o Build Insights na Seleção>Recompilar. Você também pode clicar com o botão direito do mouse em um projeto no gerenciador de soluções e escolher Executar o Build Insights>Recompilar. Escolhemos Recompilar, em vez de Compilar, para medir o tempo de compilação de todo o projeto, conforme anteriormente, e não apenas para os poucos arquivos que podem estar sujos no momento.
O tempo de compilação irá de 25,181 segundos para 13,376 segundos e a função performPhysicsCalculations
não será mais exibida no modo de exibição de Funções porque não contribui o suficiente para o tempo de compilação a ser contado.
Na coluna Nome da Função, performPhysicsCalculations() está realçado e marcado com um ícone de fogo.:::
O tempo de Sessão de Diagnóstico é o tempo geral necessário para fazer a compilação e qualquer sobrecarga para coletar os dados do Build Insights.
A próxima etapa seria criar o perfil do aplicativo para ver se o desempenho do aplicativo é afetado negativamente pela alteração. Se for, podemos adicionar __forceinline
de volta seletivamente conforme necessário.
Navegue até o código-fonte
Clique duas vezes, clique com o botão direito do mouse ou pressione Enter enquanto estiver em um arquivo no modo de exibição de Funções para abrir o código-fonte desse arquivo.
Dicas
- Você pode usar a opção Arquivo>Salvar como no arquivo de ETL em um local mais permanente para manter um registro do tempo de compilação. Em seguida, você pode compará-lo com compilações futuras para ver se suas alterações estão melhorando o tempo de compilação.
- Se você fechar inadvertidamente a janela do Build Insights, reabra-a encontrando o arquivo
<dateandtime>.etl
na sua pasta temporária. A variável de ambiente do WindowsTEMP
fornece o caminho da pasta de arquivos temporários. - Para pesquisar os dados do Build Insights com o WPA (Windows Performance Analyzer), clique no botão Abrir no WPA na parte inferior direita da janela ETL.
- Arraste as colunas para alterar a ordem das colunas. Por exemplo, você pode preferir mover a coluna Tempo 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.
- O modo de exibição de Funções fornece uma caixa de filtro para localizar uma função na qual você está interessado. Ele faz correspondências parciais no nome que você fornece.
- Se você esquecer como interpretar o que o modo de exibição de Funções está tentando mostrar, passe o mouse sobre a guia para ver uma dica de ferramenta que descreve a exibição. Se você passar o mouse sobre a guia Funções, a dica de ferramenta indica: "Exibição que mostra estatísticas para funções em que os nós filhos são funções com Forceinline".
Solução de problemas
- Se a janela do Build Insights não for exibida, faça uma recompilação, em vez de uma compilação. A janela do Build Insights não será exibida se nada realmente for compilado, o que pode ser o caso se nenhum arquivo foi alterado desde a última compilação.
- Se o modo de exibição de Funções não mostrar nenhuma função, talvez você não esteja criando com as configurações de otimização corretas. Verifique se você está compilando a Versão 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 será exibido na lista.
Confira também
Funções embutidas (C++)
Compilações C++ mais rápidas e simplificadas: uma nova métrica para o tempo
Build Insights no vídeo do Visual Studio – Pure Virtual C++ 2023
Solucionar problemas de impacto do arquivo de cabeçalho no tempo de compilação
Modo de Exibição de Funções do Build 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