Como posso criar um perfil do código C ++ em execução no Linux?
1816
Eu tenho um aplicativo C ++, em execução no Linux, que estou otimizando. Como posso identificar quais áreas do meu código estão sendo executadas lentamente?
Se você fornecer mais dados sobre sua pilha de desenvolvimento, poderá obter melhores respostas. Existem criadores de perfil da Intel e da Sun, mas você precisa usar seus compiladores. Isso é uma opção?
A maioria das respostas são codeperfis. No entanto, inversão de prioridade, alias de cache, contenção de recursos etc. podem ser fatores de otimização e desempenho. Eu acho que as pessoas leem informações no meu código lento . As perguntas frequentes estão referenciando este tópico.
Eu costumava usar o pstack aleatoriamente, na maioria das vezes imprime a pilha mais típica onde o programa está na maior parte do tempo, apontando para o gargalo.
Jose Manuel Gomez Alvarez
Respostas:
1406
Se seu objetivo é usar um criador de perfil, use um dos sugeridos.
No entanto, se você estiver com pressa e puder interromper manualmente seu programa no depurador enquanto estiver subjetivamente lento, há uma maneira simples de encontrar problemas de desempenho.
Basta parar várias vezes e, a cada vez, observe a pilha de chamadas. Se houver algum código que está desperdiçando uma porcentagem do tempo, 20% ou 50% ou o que for, essa é a probabilidade de você pegá-lo no ato em cada amostra. Portanto, essa é aproximadamente a porcentagem de amostras nas quais você a verá. Não é necessário adivinhação educada. Se você tem um palpite sobre qual é o problema, isso o provará ou não.
Você pode ter vários problemas de desempenho de tamanhos diferentes. Se você limpar qualquer uma delas, as restantes terão uma porcentagem maior e serão mais fáceis de identificar nas passagens subsequentes. Esse efeito de ampliação , quando composto por vários problemas, pode levar a fatores de aceleração realmente maciços.
Advertência : os programadores tendem a ser céticos em relação a essa técnica, a menos que a tenham usado por si próprios. Eles dizem que os criadores de perfil fornecem essas informações, mas isso só é verdade se eles coletarem toda a pilha de chamadas e permitirem que você examine um conjunto aleatório de amostras. (Os resumos são onde a percepção é perdida.) Os gráficos de chamada não fornecem as mesmas informações, porque
Eles não resumem no nível da instrução e
Eles fornecem resumos confusos na presença de recursão.
Eles também dirão que só funciona em programas de brinquedo, quando na verdade funciona em qualquer programa, e parece funcionar melhor em programas maiores, porque eles tendem a ter mais problemas para encontrar. Eles dizem que às vezes encontra coisas que não são problemas, mas isso só é verdade se você vir algo uma vez . Se você vir um problema em mais de uma amostra, é real.
PS Isso também pode ser feito em programas multithread se houver uma maneira de coletar amostras da pilha de chamadas do pool de threads em um determinado momento, como no Java.
PPS Como uma generalidade aproximada, quanto mais camadas de abstração você tiver em seu software, maior a probabilidade de descobrir que essa é a causa dos problemas de desempenho (e a oportunidade de acelerar).
Adicionado : pode não ser óbvio, mas a técnica de amostragem por pilha funciona igualmente bem na presença de recursão. O motivo é que o tempo que seria economizado pela remoção de uma instrução é aproximado pela fração de amostras que a contêm, independentemente do número de vezes que isso pode ocorrer em uma amostra.
Outra objeção que sempre ouço é: " Ele irá parar em algum lugar aleatoriamente e perderá o problema real ". Isso vem de ter um conceito prévio de qual é o verdadeiro problema. Uma propriedade chave dos problemas de desempenho é que eles desafiam as expectativas. A amostragem diz que algo é um problema e sua primeira reação é a descrença. Isso é natural, mas você pode ter certeza que, se encontrar um problema, é real e vice-versa.
Adicionado : Deixe-me fazer uma explicação bayesiana de como funciona. Suponha que exista alguma instrução I(chamada ou não) que esteja na pilha de chamadas em uma fração fdo tempo (e, portanto, custa muito). Por simplicidade, suponha que não sabemos o que fé, mas suponha que seja 0,1, 0,2, 0,3, ... 0,9, 1,0 e a probabilidade anterior de cada uma dessas possibilidades seja 0,1, portanto todos esses custos são igualmente prováveis a priori.
Então suponha que tomemos apenas 2 amostras de pilha e vemos instruções Iem ambas as amostras, observação designada o=2/2. Isso nos fornece novas estimativas da frequência fde I, de acordo com o seguinte:
Prior
P(f=x) x P(o=2/2|f=x) P(o=2/2&&f=x) P(o=2/2&&f >= x) P(f >= x | o=2/2)0.1110.10.10.259740260.10.90.810.0810.1810.470129870.10.80.640.0640.2450.6363636360.10.70.490.0490.2940.7636363640.10.60.360.0360.330.8571428570.10.50.250.0250.3550.9220779220.10.40.160.0160.3710.9636363640.10.30.090.0090.380.9870129870.10.20.040.0040.3840.9974025970.10.10.010.0010.3851
P(o=2/2)0.385
A última coluna diz que, por exemplo, a probabilidade de que f> = 0,5 seja 92%, acima da suposição anterior de 60%.
Suponha que as suposições anteriores sejam diferentes. Suponha que assumimos que P(f=0.1)seja 0,991 (quase certo) e que todas as outras possibilidades sejam quase impossíveis (0,001). Em outras palavras, nossa certeza anterior é que Ié barato. Então temos:
Prior
P(f=x) x P(o=2/2|f=x) P(o=2/2&& f=x) P(o=2/2&&f >= x) P(f >= x | o=2/2)0.001110.0010.0010.0727272730.0010.90.810.000810.001810.1316363640.0010.80.640.000640.002450.1781818180.0010.70.490.000490.002940.2138181820.0010.60.360.000360.00330.240.0010.50.250.000250.003550.2581818180.0010.40.160.000160.003710.2698181820.0010.30.090.000090.00380.2763636360.0010.20.040.000040.003840.2792727270.9910.10.010.009910.013751
P(o=2/2)0.01375
Agora ele diz que P(f >= 0.5)é 26%, acima da suposição anterior de 0,6%. Então Bayes nos permite atualizar nossa estimativa do custo provável de I. Se a quantidade de dados é pequena, não nos diz com precisão qual é o custo, apenas que é grande o suficiente para valer a pena consertar.
Ainda outra maneira de olhar para isso é chamada de regra de sucessão . Se você jogar uma moeda 2 vezes e ela aparecer nas duas vezes, o que isso diz sobre o provável peso da moeda? A maneira respeitada de responder é dizer que é uma distribuição Beta, com valor médio (number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75%.
(A chave é que vemos Imais de uma vez. Se a vemos apenas uma vez, isso não nos diz muito, exceto que f> 0.)
Portanto, mesmo um número muito pequeno de amostras pode nos dizer muito sobre o custo das instruções que ele vê. (E ele vai vê-los com uma frequência, em média, proporcional ao seu custo. Se nas amostras são colhidas, e fé o custo, em seguida, Ivai aparecer em nf+/-sqrt(nf(1-f))amostras. Exemplo, n=10, f=0.3, isto é 3+/-1.4amostras.)
Adicionado : para dar uma idéia intuitiva da diferença entre medição e amostragem aleatória de pilhas:
agora existem criadores de perfil que fazem a amostragem da pilha, mesmo no horário do relógio de parede, mas o que sai são medições (ou hot path, ou hot spot, dos quais um "gargalo" pode ocultar facilmente). O que eles não mostram (e poderiam facilmente) são as próprias amostras. E se seu objetivo é encontrar o gargalo, o número deles que você precisa ver é, em média , 2 dividido pela fração do tempo que leva. Portanto, se levar 30% do tempo, 2 / .3 = 6,7 amostras, em média, a mostrarão, e a chance de 20 amostras mostrarem é de 99,2%.
Aqui está uma ilustração simplificada da diferença entre examinar medições e examinar amostras de pilhas. O gargalo pode ser um grande blob como esse, ou vários pequenos, não faz diferença.
A medição é horizontal; indica a fração do tempo que as rotinas específicas levam. A amostragem é vertical. Se houver alguma maneira de evitar o que todo o programa está fazendo naquele momento, e se você o vir em uma segunda amostra , encontrou o gargalo. É isso que faz a diferença - ver toda a razão do tempo gasto, não apenas quanto.
Esse é basicamente o perfil de amostragem de um homem pobre, o que é ótimo, mas você corre o risco de um tamanho de amostra muito pequeno que possivelmente fornecerá resultados totalmente espúrios.
Crashworks 22/05/09
100
@ Bater: Não vou discutir a parte do "pobre homem" :-) É verdade que a precisão da medição estatística requer muitas amostras, mas existem dois objetivos conflitantes - medição e localização do problema. Estou focando no último, para o qual você precisa de precisão de localização, não de precisão de medida. Por exemplo, pode haver, no meio da pilha, uma única chamada de função A (); isso representa 50% do tempo, mas pode estar em outra grande função B, juntamente com muitas outras chamadas para A () que não são caras. Resumos precisos dos tempos de função podem ser uma pista, mas todas as outras amostras de pilhas identificarão o problema.
Mike Dunlavey
41
... o mundo parece pensar que um gráfico de chamadas, anotado com contagem de chamadas e / ou tempo médio, é bom o suficiente. Não é. E a parte triste é que, para aqueles que experimentam a pilha de chamadas, as informações mais úteis estão bem na frente delas, mas jogam fora, no interesse das "estatísticas".
Mike Dunlavey 24/05/09
30
Não pretendo discordar de sua técnica. Claramente, confio bastante nos perfis de amostragem que caminham em pilhas. Estou apenas apontando que agora existem algumas ferramentas que o fazem de maneira automatizada, o que é importante quando você ultrapassa o ponto de obter uma função de 25% a 15% e precisa reduzi-la de 1,2% para 0,6%.
Crashworks
13
-1: Boa ideia, mas se você está sendo pago para trabalhar mesmo em um ambiente moderadamente orientado para o desempenho, isso é uma perda de tempo de todos. Use um criador de perfil real para que não tenhamos que ir atrás de você e resolver os problemas reais.
Irá gerar um arquivo chamado callgrind.out.x. Você pode usar a kcachegrindferramenta para ler este arquivo. Ele fornecerá uma análise gráfica de coisas com resultados como quais linhas custam quanto.
valgrind é ótimo, mas esteja avisado de que ele tornará seu programa muito lento
neves
30
Confira também o Gprof2Dot para uma maneira alternativa incrível de visualizar a saída. ./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
22413 Sebastian
2
@neves Sim O Valgrind não é muito útil em termos de velocidade para a criação de perfis de aplicativos "gstreamer" e "opencv" em tempo real.
Concordo que o gprof é o padrão atual. Apenas uma observação, porém, Valgrind é usado para analisar vazamentos de memória e outros aspectos relacionados à memória de seus programas, não para otimização de velocidade.
Bill o Lagarto
68
Bill, na suíte vaglrind, você pode encontrar callgrind e massif. Ambos são bastante úteis para aplicativos de perfil
O gprof -pg é apenas uma aproximação do perfil do pilha de chamadas. Ele insere chamadas mcount para rastrear quais funções estão chamando quais outras funções. Ele usa amostragem com base no tempo padrão para, uh, tempo. Em seguida, distribui os tempos amostrados em uma função foo () de volta para os chamadores de foo (), de acordo com o número de chamadas. Portanto, não faz distinção entre chamadas de custos diferentes.
Krazy Glew
1
Com o clang / clang ++, pode-se considerar o uso do criador de perfil de CPU do gperftools . Advertência: Eu não fiz isso sozinho.
einpoklum
257
Os kernels mais recentes (por exemplo, os kernels mais recentes do Ubuntu) vêm com as novas ferramentas 'perf' ( apt-get install linux-tools) AKA perf_events .
O importante é que essas ferramentas podem ter perfil de sistema e não apenas perfil de processo - elas podem mostrar a interação entre threads, processos e kernel e permitem entender o agendamento e as dependências de E / S entre processos.
Ótima ferramenta! Existe alguma maneira de obter uma visualização típica de "borboleta" que começa no estilo "main-> func1-> fun2"? Eu não consigo descobrir isso ... perf reportparece-me dar os nomes das funções com os pais chamada ... (por isso é uma espécie de visão borboleta invertido)
kizzx2
Will pode mostrar o gráfico de tempo da atividade do thread; com informações de número de CPU adicionadas? Quero ver quando e qual thread estava sendo executado em todas as CPUs.
Osgx
2
@ kizzx2 - você pode usar gprof2dote perf script. Ferramenta muito boa!
Outra introdução bom perfexiste em archive.li/9r927#selection-767.126-767.271 (Por que os deuses por isso decidiu excluir a página da base de conhecimento SO está além de mim ....)
ragerdl
75
Eu usaria Valgrind e Callgrind como base para o meu conjunto de ferramentas de criação de perfil. O que é importante saber é que o Valgrind é basicamente uma máquina virtual:
(wikipedia) O Valgrind é essencialmente uma máquina virtual usando técnicas de compilação just-in-time (JIT), incluindo recompilação dinâmica. Nada do programa original é executado diretamente no processador host. Em vez disso, o Valgrind converte o programa em um formato temporário e simples, chamado Intermediate Representation (IR), que é um formulário baseado em SSA e neutro em processador. Após a conversão, uma ferramenta (veja abaixo) fica livre para fazer as transformações que desejar no IR, antes que o Valgrind converta o IR novamente em código de máquina e permita que o processador host a execute.
O Callgrind é um construtor de perfil baseado nisso. O principal benefício é que você não precisa executar seu aplicativo por horas para obter resultados confiáveis. Mesmo uma segunda execução é suficiente para obter resultados sólidos e confiáveis, porque o Callgrind é um criador de perfil sem análise.
Outra ferramenta construída sobre Valgrind é o Massif. Eu o uso para criar perfil de uso de memória heap. Isso funciona muito bem. O que ele faz é fornecer instantâneos do uso da memória - informações detalhadas O que contém QUALQUER porcentagem de memória e a OMS a colocou lá. Essas informações estão disponíveis em diferentes momentos da execução do aplicativo.
A resposta a ser executada valgrind --tool=callgrindnão é completa sem algumas opções. Normalmente, não queremos criar um perfil de 10 minutos de tempo de inicialização lento no Valgrind e queremos criar um perfil do nosso programa quando ele estiver executando alguma tarefa.
Então é isso que eu recomendo. Execute o programa primeiro:
Agora, quando ele funciona e queremos iniciar a criação de perfil, devemos executar em outra janela:
callgrind_control -i on
Isso ativa a criação de perfil. Para desativá-lo e interromper toda a tarefa, podemos usar:
callgrind_control -k
Agora, temos alguns arquivos chamados callgrind.out. * No diretório atual. Para ver os resultados da criação de perfil, use:
kcachegrind callgrind.out.*
Eu recomendo na próxima janela clicar no cabeçalho da coluna "Auto", caso contrário, mostra que "main ()" é a tarefa mais demorada. "Self" mostra quanto cada função em si levou tempo, não em conjunto com os dependentes.
Agora, por algum motivo, os arquivos callgrind.out. * Estavam sempre vazios. A execução de callgrind_control -d foi útil para forçar o despejo de dados no disco.
Samuel Samuel 31/07
3
Não pode. Meus contextos usuais são algo como MySQL ou PHP inteiro ou algo semelhante. Muitas vezes nem sei o que quero separar no começo.
Samuel Samuel 21/11
2
Ou, no meu caso, meu programa realmente carrega um monte de dados em um cache LRU, e eu não quero criar um perfil. Portanto, forço o carregamento de um subconjunto do cache na inicialização e perfil o código usando apenas esses dados (permitindo que a CPU do OS + gerencie o uso de memória no meu cache). Funciona, mas o carregamento desse cache é lento e exige muita CPU em todo o código que estou tentando criar um perfil em um contexto diferente; portanto, o callgrind produz resultados muito poluídos.
Uso o Gprof nos últimos dias e já encontrei três limitações significativas, uma das quais ainda não vi documentada em nenhum outro lugar:
Ele não funciona corretamente no código multithread, a menos que você use uma solução alternativa
O gráfico de chamada fica confuso pelos ponteiros de função. Exemplo: Eu tenho uma função chamada multithread()que permite multiencadear uma função especificada em uma matriz especificada (ambas passadas como argumentos). O Gprof, no entanto, vê todas as chamadas multithread()como equivalentes para fins de computação do tempo gasto em crianças. Como algumas funções passam multithread()mais tempo do que outras, meus gráficos de chamada são inúteis. (Para aqueles que se perguntam se o encadeamento é o problema aqui: não, multithread()pode , opcionalmente, e nesse caso, executar tudo sequencialmente apenas no encadeamento de chamada).
Diz aqui que "... os números de número de chamadas são obtidos por contagem, não por amostragem. Eles são completamente precisos ...". No entanto, acho que meu gráfico de chamadas me fornece 5345859132 + 784984078 como estatísticas de chamadas para minha função mais chamada, onde o primeiro número deve ser direto e as segundas chamadas recursivas (que são todas por si só). Como isso implicava que eu tinha um bug, coloquei contadores longos (64 bits) no código e fiz a mesma execução novamente. Minhas contas: 5345859132 chamadas diretas e 78094395406 auto-recursivas. Existem muitos dígitos, por isso vou apontar que as chamadas recursivas que medem são 78 bilhões, contra 784m do Gprof: um fator de 100 diferente. Ambas as execuções eram de código único e não otimizado, uma compilada -ge outra -pg.
Este foi o GNU Gprof (GNU Binutils para Debian) 2.18.0.20080103 sendo executado no Debian Lenny de 64 bits, se isso ajudar alguém.
Sim, faz amostragem, mas não para números de número de chamadas. Curiosamente, seguir o seu link levou-me a uma versão atualizada da página de manual à qual vinculei em minha postagem, um novo URL: sourceware.org/binutils/docs/gprof/… Isso repete a citação na parte (iii) da minha resposta, mas também diz "Em aplicativos multithread ou aplicativos single threaded que se vinculam a bibliotecas multithread, as contagens são determinísticas apenas se a função de contagem for segura para threads. -seguro)."
Rob_before_edits 22/06
Não está claro para mim se isso explica meu resultado em (iii). Meu código foi vinculado -lpthread -lm e declarou uma variável estática "pthread_t * thr" e "pthread_mutex_t nextLock = PTHREAD_MUTEX_INITIALIZER", mesmo quando estava executando thread único. Normalmente, eu presumiria que "vincular-se a bibliotecas multiencadeadas" significa realmente usar essas bibliotecas e, em maior medida do que isso, mas posso estar errado!
22612 Robbefore_edits
23
Use Valgrind, callgrind e kcachegrind:
valgrind --tool=callgrind ./(Your binary)
gera callgrind.out.x. Leia-o usando o kcachegrind.
Use o gprof (add -pg):
cc -o myprog myprog.c utils.c -g -pg
(não é tão bom para multi-threads, ponteiros de função)
Use o google-perftools:
Usa amostragem de tempo, gargalos de E / S e CPU são revelados.
O Intel VTune é o melhor (gratuito para fins educacionais).
Nesta resposta, usarei várias ferramentas diferentes para analisar alguns programas de teste muito simples, a fim de comparar concretamente como essas ferramentas funcionam.
O seguinte programa de teste é muito simples e faz o seguinte:
mainchamadas faste maybe_slow3 vezes, uma das maybe_slowchamadas que estão sendo lenta
A chamada lenta de maybe_slowé 10x mais longa e domina o tempo de execução se considerarmos as chamadas para a função filho common. Idealmente, a ferramenta de criação de perfil poderá apontar para a chamada lenta específica.
both faste maybe_slowcall common, que representam a maior parte da execução do programa
A interface do programa é:
./main.out [n [seed]]
e o programa faz O(n^2)loops no total. seedé apenas obter uma saída diferente sem afetar o tempo de execução.
main.c
#include<inttypes.h>#include<stdio.h>#include<stdlib.h>uint64_t __attribute__ ((noinline)) common(uint64_t n,uint64_t seed){for(uint64_t i =0; i < n;++i){
seed =(seed * seed)-(3* seed)+1;}return seed;}uint64_t __attribute__ ((noinline)) fast(uint64_t n,uint64_t seed){uint64_t max =(n /10)+1;for(uint64_t i =0; i < max;++i){
seed = common(n,(seed * seed)-(3* seed)+1);}return seed;}uint64_t __attribute__ ((noinline)) maybe_slow(uint64_t n,uint64_t seed,int is_slow){uint64_t max = n;if(is_slow){
max *=10;}for(uint64_t i =0; i < max;++i){
seed = common(n,(seed * seed)-(3* seed)+1);}return seed;}int main(int argc,char**argv){uint64_t n, seed;if(argc >1){
n = strtoll(argv[1], NULL,0);}else{
n =1;}if(argc >2){
seed = strtoll(argv[2], NULL,0);}else{
seed =0;}
seed += maybe_slow(n, seed,0);
seed += fast(n, seed);
seed += maybe_slow(n, seed,1);
seed += fast(n, seed);
seed += maybe_slow(n, seed,0);
seed += fast(n, seed);
printf("%" PRIX64 "\n", seed);return EXIT_SUCCESS;}
gprof
O gprof requer recompilar o software com a instrumentação e também usa uma abordagem de amostragem junto com essa instrumentação. Portanto, ele encontra um equilíbrio entre precisão (a amostragem nem sempre é totalmente precisa e pode pular funções) e desaceleração da execução (instrumentação e amostragem são técnicas relativamente rápidas que não diminuem muito a execução).
O gprof é incorporado ao GCC / binutils, então tudo o que precisamos fazer é compilar com a -pgopção de ativar o gprof. Em seguida, executamos o programa normalmente com um parâmetro CLI de tamanho que produz uma duração razoável de alguns segundos ( 10000):
Por motivos educacionais, também faremos uma corrida sem as otimizações ativadas. Observe que isso é inútil na prática, pois você normalmente se preocupa apenas em otimizar o desempenho do programa otimizado:
Primeiro, timenos diz que o tempo de execução com e sem -pgo mesmo foi, o que é ótimo: sem lentidão! No entanto, vi relatos de desacelerações 2x - 3x em software complexo, por exemplo, como mostrado neste tíquete .
Como compilamos -pg, a execução do programa produz um gmon.outarquivo contendo os dados de criação de perfil.
Aqui, a gprofferramenta lê as gmon.outinformações de rastreamento e gera um relatório legível por humanos main.gprof, que é gprof2dotlido para gerar um gráfico.
A -O0saída é praticamente auto-explicativa. Por exemplo, mostra que as 3 maybe_slowchamadas e as chamadas filho ocupam 97,56% do tempo de execução total, embora a execução de maybe_slowsi mesma sem filhos represente 0,00% do tempo total de execução, ou seja, quase todo o tempo gasto nessa função foi gasto em chamadas de criança.
TODO: por que está mainfaltando na -O3saída, mesmo que eu possa vê-la em um btno GDB? Função ausente da saída do GProf Eu acho que é porque o gprof também está baseado em amostragem, além de sua instrumentação compilada, e -O3mainé muito rápido e não tem amostras.
Eu escolho a saída SVG em vez de PNG porque o SVG é pesquisável com Ctrl + F e o tamanho do arquivo pode ser cerca de 10x menor. Além disso, a largura e a altura da imagem gerada podem ser enormes, com dezenas de milhares de pixels para softwares complexos, e o GNOME eog3.28.1 aparece nesse caso para PNGs, enquanto os SVGs são abertos automaticamente pelo meu navegador. O gimp 2.8 funcionou bem, veja também:
mas, mesmo assim, você arrastará muito a imagem para encontrar o que deseja; veja, por exemplo, esta imagem de um exemplo de software "real" extraído deste ticket :
Você consegue encontrar facilmente a pilha de chamadas mais crítica com todas essas minúsculas linhas de espaguete sem classificação passando uma sobre a outra? Pode haver dotopções melhores , tenho certeza, mas não quero ir para lá agora. O que realmente precisamos é de um visualizador dedicado adequado, mas ainda não encontrei um:
No entanto, você pode usar o mapa de cores para atenuar um pouco esses problemas. Por exemplo, na enorme imagem anterior, finalmente consegui encontrar o caminho crítico à esquerda quando fiz a dedução brilhante de que o verde vem depois do vermelho, seguido finalmente do azul mais escuro e mais escuro.
Como alternativa, também podemos observar a saída de texto da gprofferramenta binutils incorporada, que salvamos anteriormente em:
cat main.gprof
Por padrão, isso produz uma saída extremamente detalhada que explica o que os dados de saída significam. Como não posso explicar melhor do que isso, vou deixar você ler você mesmo.
Depois de entender o formato de saída de dados, você pode reduzir a verbosidade para mostrar apenas os dados sem o tutorial com a -bopção:
gprof -b main.out
No nosso exemplo, os resultados foram para -O0:
Flat profile:Each sample counts as 0.01 seconds.% cumulative self self total
time seconds seconds calls s/call s/call name
100.353.673.671230030.000.00 common
0.003.670.0030.000.03 fast
0.003.670.0030.001.19 maybe_slow
Call graph
granularity: each sample hit covers 2 byte(s)for0.27% of 3.67 seconds
index % time self children called name
0.090.003003/123003 fast [4]3.580.00120000/123003 maybe_slow [3][1]100.03.670.00123003 common [1]-----------------------------------------------<spontaneous>[2]100.00.003.67 main [2]0.003.583/3 maybe_slow [3]0.000.093/3 fast [4]-----------------------------------------------0.003.583/3 main [2][3]97.60.003.583 maybe_slow [3]3.580.00120000/123003 common [1]-----------------------------------------------0.000.093/3 main [2][4]2.40.000.093 fast [4]0.090.003003/123003 common [1]-----------------------------------------------Index by function name
[1] common [4] fast [3] maybe_slow
e para -O3:
Flat profile:Each sample counts as 0.01 seconds.% cumulative self self total
time seconds seconds calls us/call us/call name
100.521.841.8412300314.9614.96 common
Call graph
granularity: each sample hit covers 2 byte(s)for0.54% of 1.84 seconds
index % time self children called name
0.040.003003/123003 fast [3]1.790.00120000/123003 maybe_slow [2][1]100.01.840.00123003 common [1]-----------------------------------------------<spontaneous>[2]97.60.001.79 maybe_slow [2]1.790.00120000/123003 common [1]-----------------------------------------------<spontaneous>[3]2.40.000.04 fast [3]0.040.003003/123003 common [1]-----------------------------------------------Index by function name
[1] common
Como um resumo muito rápido para cada seção, por exemplo:
0.003.583/3 main [2][3]97.60.003.583 maybe_slow [3]3.580.00120000/123003 common [1]
centra-se na função que é deixada recuada ( maybe_flow). [3]é o ID dessa função. Acima da função, estão os chamadores e, abaixo, os callees.
Pois -O3, veja aqui como na saída gráfica que maybe_slowe fastnão possui um pai conhecido, que é o que a documentação diz que <spontaneous>significa.
callgrind é a ferramenta do valgrind para criar um perfil do código e o kcachegrind é um programa do KDE que pode visualizar a saída do cachegrind.
Primeiro, precisamos remover o -pgsinalizador para voltar à compilação normal, caso contrário, a execução falhará comProfiling timer expired , sim, isso é tão comum que eu fiz e houve uma pergunta de estouro de pilha.
Eu habilito --dump-instr=yes --collect-jumps=yes porque isso também despeja informações que nos permitem exibir uma quebra de desempenho por linha de montagem, a um custo adicional relativamente pequeno.
Logo de cara, timediz-nos que o programa levou 29,5 segundos para ser executado; portanto, tivemos um abrandamento de cerca de 15x neste exemplo. Claramente, essa desaceleração será uma limitação séria para cargas de trabalho maiores. No "exemplo de software do mundo real" mencionado aqui , observei uma desaceleração de 80x.
A execução gera um arquivo de dados de perfil chamado, callgrind.out.<pid>por exemplo, callgrind.out.8554no meu caso. Vemos esse arquivo com:
kcachegrind callgrind.out.8554
que mostra uma GUI que contém dados semelhantes à saída textual do gprof:
Além disso, se formos na parte inferior direita da guia "Call Graph", vemos um gráfico de chamada que podemos exportar clicando com o botão direito do mouse para obter a seguinte imagem com quantidades irracionais de borda branca :-)
Acho que fastnão está aparecendo nesse gráfico porque o kcachegrind deve ter simplificado a visualização, porque essa chamada leva muito pouco tempo; esse provavelmente será o comportamento que você deseja em um programa real. O menu do botão direito do mouse possui algumas configurações para controlar quando selecionar esses nós, mas não consegui mostrar uma ligação tão curta depois de uma tentativa rápida. Se eu clicar na fastjanela esquerda, ele mostra um gráfico de chamada com fast, de modo que a pilha foi realmente capturada. Ninguém ainda havia encontrado uma maneira de mostrar o gráfico de chamada do gráfico completo: Faça com que o callgrind mostre todas as chamadas de função no gráfico de chamada kcachegrind
TODO em software C ++ complexo, vejo algumas entradas do tipo <cycle N>, por exemplo, <cycle 11>onde eu esperaria nomes de funções, o que isso significa? Percebi que existe um botão "Detecção de ciclo" para ativar e desativar esse recurso, mas o que isso significa?
perf de linux-tools
perfparece usar exclusivamente mecanismos de amostragem de kernel do Linux. Isso facilita a configuração, mas também não é totalmente preciso.
sudo apt install linux-tools
time perf record -g ./main.out 10000
Isso adicionou 0,2s à execução, por isso estamos bem no tempo, mas ainda não vejo muito interesse depois de expandir o commonnó com a seta à direita do teclado:
TODO: o que aconteceu na -O3execução? É simplesmente isso maybe_slowe fastfoi rápido demais e não obteve nenhuma amostra? Funciona bem -O3em programas maiores que demoram mais para serem executados? Perdi alguma opção de CLI? Eu descobri que estava prestes -Fa controlar a frequência da amostra no Hertz, mas -F 39500aumentei para o máximo permitido por padrão de (poderia ser aumentado comsudo ) e ainda não vejo chamadas claras.
Mas isso tem a desvantagem de que você precisa primeiro converter os dados para o Common Trace Format, o que pode ser feito perf data --to-ctf, mas precisa ser ativado no tempo de compilação / ter perfnovo o suficiente, um dos quais não é o caso do perf Ubuntu 18.04
Em seguida, podemos ativar o perfilador de CPU gperftools de duas maneiras: em tempo de execução ou em tempo de construção.
Em tempo de execução, precisamos passar o conjunto LD_PRELOADpara apontar para o libprofiler.soqual você pode encontrar locate libprofiler.so, por exemplo, no meu sistema:
A maneira mais agradável de visualizar esses dados que encontrei até agora é tornar a saída do pprof no mesmo formato que o kcachegrind usa como entrada (sim, a ferramenta Valgrind-project-viewer-tool) e usar o kcachegrind para visualizar o seguinte:
Após executar com qualquer um desses métodos, obtemos um prof.outarquivo de dados de perfil como saída. Podemos visualizar esse arquivo graficamente como um SVG com:
google-pprof --web main.out prof.out
que fornece um gráfico de chamada familiar como outras ferramentas, mas com a unidade desajeitada do número de amostras em vez de segundos.
Como alternativa, também podemos obter alguns dados textuais com:
google-pprof --text main.out prof.out
que dá:
Using local file main.out.Using local file prof.out.Total:187 samples
187100.0%100.0%187100.0% common
00.0%100.0%187100.0% __libc_start_main
00.0%100.0%187100.0% _start
00.0%100.0%42.1% fast
00.0%100.0%187100.0% main
00.0%100.0%18397.9% maybe_slow
Por padrão, o registro perf usa o registro de ponteiro de quadro. Os compiladores modernos não registram o endereço do quadro e, em vez disso, usam o registro como um propósito geral. A alternativa é compilar com o -fno-omit-frame-pointersinalizador ou usar uma alternativa diferente: grave com --call-graph "dwarf"ou --call-graph "lbr"dependendo do seu cenário.
Jorge Bellon
5
Para programas de thread único, você pode usar o igprof , The Ignominous Profiler: https://igprof.org/ .
É um perfilador de amostragem, seguindo as linhas da ... resposta longa ... de Mike Dunlavey, que embrulhará os resultados em uma árvore de pilha de chamadas navegável, anotada com o tempo ou a memória gasta em cada função, cumulativa ou por função.
Parece interessante, mas falha ao compilar com o GCC 9.2. (Debian / Sid) Eu fiz um problema no github.
Basile Starynkevitch 11/01
5
Também vale a pena mencionar são
HPCToolkit ( http://hpctoolkit.org/ ) - Código aberto, funciona para programas paralelos e possui uma GUI com a qual é possível ver os resultados de várias maneiras
Eu usei o HPCToolkit e o VTune e eles são muito eficazes para encontrar o pólo longo na barraca e não precisam que seu código seja recompilado (exceto que você precisa usar a construção do tipo -g -O ou RelWithDebInfo no CMake para obter uma saída significativa) . Ouvi dizer que a TAU é semelhante em recursos.
Estes são os dois métodos que eu uso para acelerar meu código:
Para aplicativos vinculados à CPU:
Use um profiler no modo DEBUG para identificar partes questionáveis do seu código
Em seguida, mude para o modo RELEASE e comente as seções questionáveis do seu código (stub-lo com nada) até ver mudanças no desempenho.
Para aplicativos de ligação de E / S:
Use um criador de perfil no modo RELEASE para identificar partes questionáveis do seu código.
NB
Se você não possui um perfilador, use o perfil do pobre homem. Clique em pausa enquanto depura seu aplicativo. A maioria dos pacotes de desenvolvedores entra em montagem com números de linhas comentados. Você tem probabilidade estatisticamente de pousar em uma região que está consumindo a maior parte dos ciclos de sua CPU.
Para a CPU, o motivo da criação de perfil no modo DEBUG é que, se você tentar criar um perfil no modo RELEASE , o compilador reduzirá a matemática, vetorizar loops e funções em linha que tendem a transformar seu código em uma bagunça não mapeada quando montado. Uma bagunça não mapeável significa que seu criador de perfil não será capaz de identificar claramente o que está demorando tanto, porque o assembly pode não corresponder ao código-fonte em otimização . Se você precisar do desempenho (por exemplo, sensível ao tempo) do modo RELEASE , desative os recursos do depurador conforme necessário para manter um desempenho utilizável.
Para o limite de E / S, o criador de perfil ainda pode identificar operações de E / S no modo RELEASE , porque as operações de E / S são vinculadas externamente a uma biblioteca compartilhada (na maioria das vezes) ou, na pior das hipóteses, resultam em um sistema. vetor de interrupção de chamada (que também é facilmente identificável pelo criador de perfil).
+1 O método do pobre homem funciona tão bem para o limite de E / S quanto para o limite da CPU, e eu recomendo fazer todo o ajuste de desempenho no modo DEBUG. Quando terminar de sintonizar, ative RELEASE. Isso fará uma melhoria se o programa estiver vinculado à CPU no seu código. Aqui está um vídeo bruto, mas breve, do processo.
Mike Dunlavey
3
Eu não usaria compilações DEBUG para criação de perfil de desempenho. Muitas vezes, vi que partes críticas de desempenho no modo DEBUG são completamente otimizadas no modo de liberação. Outro problema é o uso de declarações no código de depuração que adicionam ruído ao desempenho.
precisa saber é o seguinte
3
Você leu meu post? "Se você precisar do desempenho (por exemplo, sensível ao tempo) do modo RELEASE, desative os recursos do depurador conforme necessário para manter um desempenho utilizável", "Em seguida, mude para o modo RELEASE e comente as seções questionáveis do seu código (stub sem nada) até ver mudanças no desempenho. "? Eu disse verificar possíveis áreas problemáticas no modo de depuração e verificar esses problemas no modo de lançamento para evitar a armadilha que você mencionou.
É multiplataforma e permite que você não avalie o desempenho do seu aplicativo também em tempo real. Você pode até combiná-lo com um gráfico ao vivo. Isenção total: Eu sou o autor.
Você pode usar uma estrutura de log como loguruuma vez que inclui registros de data e hora e tempo de atividade total que podem ser usados com bom gosto para criação de perfil:
No trabalho, temos uma ferramenta muito boa que nos ajuda a monitorar o que queremos em termos de agendamento. Isso tem sido útil várias vezes.
Está em C ++ e deve ser personalizado de acordo com suas necessidades. Infelizmente não posso compartilhar código, apenas conceitos. Você usa um volatilebuffer "grande" que contém carimbos de data e hora e ID do evento que pode despejar post mortem ou depois de parar o sistema de log (e despejar isso em um arquivo, por exemplo).
Você recupera o chamado buffer grande com todos os dados e uma pequena interface o analisa e mostra eventos com nome (para cima / para baixo + valor), como um osciloscópio faz com as cores (configuradas no .hpparquivo).
Você personaliza a quantidade de eventos gerados para se concentrar apenas no que deseja. Isso nos ajudou muito a agendar problemas e consumir a quantidade de CPU que desejávamos, com base na quantidade de eventos registrados por segundo.
Você precisa de 3 arquivos:
toolname.hpp // interface
toolname.cpp // code
tool_events_id.hpp // Events ID
O conceito é definir eventos tool_events_id.hppassim:
A probefunção usa algumas linhas de montagem para recuperar o registro de data e hora do relógio o mais rápido possível e, em seguida, define uma entrada no buffer. Também temos um incremento atômico para encontrar com segurança um índice onde armazenar o evento de log. Claro que o buffer é circular.
Espero que a idéia não seja ofuscada pela falta de código de exemplo.
Na verdade, um pouco surpreso, não muitos mencionados sobre o google / benchmark , embora seja um pouco complicado fixar a área específica do código, especialmente se a base de código for um pouco grande, no entanto, achei isso realmente útil quando usado em combinação comcallgrind
IMHO identificar a peça que está causando gargalo é a chave aqui. No entanto, eu tentaria responder às perguntas a seguir primeiro e escolher a ferramenta com base nisso
meu algoritmo está correto?
existem bloqueios que provam ser gargalos?
existe uma seção específica de código que está provando ser a culpada?
que tal IO, manipulado e otimizado?
valgrindcom a combinação de callrinde kcachegrinddeve fornecer uma estimativa decente dos pontos acima e, uma vez estabelecido que há problemas com alguma seção do código, eu sugiro que uma micro benchmark google benchmarkseja um bom ponto de partida.
Use o -pgsinalizador ao compilar e vincular o código e execute o arquivo executável. Enquanto esse programa é executado, os dados de criação de perfil são coletados no arquivo a.out.
Existem dois tipos diferentes de criação de perfil
1- Criação de perfil simples:
executando o comando, gprog --flat-profile a.outvocê obtém os seguintes dados
- qual a porcentagem do tempo total gasto para a função,
- quantos segundos foram gastos em uma função - incluindo e excluindo chamadas para subfunções,
- o número de chamadas,
- o tempo médio por chamada.
2- o gráfico mostra
o comando gprof --graph a.outpara obter os seguintes dados para cada função que inclui
- Em cada seção, uma função é marcada com um número de índice.
- Função acima, há uma lista de funções que chamam a função.
- Abaixo da função, há uma lista de funções que são chamadas pela função.
Como ninguém mencionou o Arm MAP, eu o adicionaria pessoalmente. Utilizei o Map com sucesso para criar um perfil de um programa científico em C ++.
O Arm MAP é o criador de perfil para códigos C, C ++, Fortran e F90 paralelos, multithread ou single threaded. Ele fornece análises detalhadas e identificação de gargalos na linha de origem. Diferentemente da maioria dos criadores de perfil, ele foi projetado para poder criar perfis de pthreads, OpenMP ou MPI para código paralelo e encadeado.
usar um software de depuração
como identificar onde o código está sendo executado lentamente?
basta pensar que você tem um obstáculo enquanto está em movimento, isso diminuirá sua velocidade
como as operações de loop, realocação indesejada de buffer de realocação, pesquisa, vazamento de memória etc. consomem mais poder de execução, afetando negativamente o desempenho do código. Certifique-se de adicionar -pg à compilação antes de criar um perfil:
g++ your_prg.cpp -pgou cc my_program.cpp -g -pgconforme seu compilador
ainda não tentei, mas ouvi coisas boas sobre o google-perftools. Definitivamente vale a pena tentar.
valgrind --tool=callgrind ./(Your binary)
Ele irá gerar um arquivo chamado gmon.out ou callgrind.out.x. Você pode usar a ferramenta kcachegrind ou depurador para ler este arquivo. Ele fornecerá uma análise gráfica de coisas com resultados como quais linhas custam quanto.
code
perfis. No entanto, inversão de prioridade, alias de cache, contenção de recursos etc. podem ser fatores de otimização e desempenho. Eu acho que as pessoas leem informações no meu código lento . As perguntas frequentes estão referenciando este tópico.Respostas:
Se seu objetivo é usar um criador de perfil, use um dos sugeridos.
No entanto, se você estiver com pressa e puder interromper manualmente seu programa no depurador enquanto estiver subjetivamente lento, há uma maneira simples de encontrar problemas de desempenho.
Basta parar várias vezes e, a cada vez, observe a pilha de chamadas. Se houver algum código que está desperdiçando uma porcentagem do tempo, 20% ou 50% ou o que for, essa é a probabilidade de você pegá-lo no ato em cada amostra. Portanto, essa é aproximadamente a porcentagem de amostras nas quais você a verá. Não é necessário adivinhação educada. Se você tem um palpite sobre qual é o problema, isso o provará ou não.
Você pode ter vários problemas de desempenho de tamanhos diferentes. Se você limpar qualquer uma delas, as restantes terão uma porcentagem maior e serão mais fáceis de identificar nas passagens subsequentes. Esse efeito de ampliação , quando composto por vários problemas, pode levar a fatores de aceleração realmente maciços.
Advertência : os programadores tendem a ser céticos em relação a essa técnica, a menos que a tenham usado por si próprios. Eles dizem que os criadores de perfil fornecem essas informações, mas isso só é verdade se eles coletarem toda a pilha de chamadas e permitirem que você examine um conjunto aleatório de amostras. (Os resumos são onde a percepção é perdida.) Os gráficos de chamada não fornecem as mesmas informações, porque
Eles também dirão que só funciona em programas de brinquedo, quando na verdade funciona em qualquer programa, e parece funcionar melhor em programas maiores, porque eles tendem a ter mais problemas para encontrar. Eles dizem que às vezes encontra coisas que não são problemas, mas isso só é verdade se você vir algo uma vez . Se você vir um problema em mais de uma amostra, é real.
PS Isso também pode ser feito em programas multithread se houver uma maneira de coletar amostras da pilha de chamadas do pool de threads em um determinado momento, como no Java.
PPS Como uma generalidade aproximada, quanto mais camadas de abstração você tiver em seu software, maior a probabilidade de descobrir que essa é a causa dos problemas de desempenho (e a oportunidade de acelerar).
Adicionado : pode não ser óbvio, mas a técnica de amostragem por pilha funciona igualmente bem na presença de recursão. O motivo é que o tempo que seria economizado pela remoção de uma instrução é aproximado pela fração de amostras que a contêm, independentemente do número de vezes que isso pode ocorrer em uma amostra.
Outra objeção que sempre ouço é: " Ele irá parar em algum lugar aleatoriamente e perderá o problema real ". Isso vem de ter um conceito prévio de qual é o verdadeiro problema. Uma propriedade chave dos problemas de desempenho é que eles desafiam as expectativas. A amostragem diz que algo é um problema e sua primeira reação é a descrença. Isso é natural, mas você pode ter certeza que, se encontrar um problema, é real e vice-versa.
Adicionado : Deixe-me fazer uma explicação bayesiana de como funciona. Suponha que exista alguma instrução
I
(chamada ou não) que esteja na pilha de chamadas em uma fraçãof
do tempo (e, portanto, custa muito). Por simplicidade, suponha que não sabemos o quef
é, mas suponha que seja 0,1, 0,2, 0,3, ... 0,9, 1,0 e a probabilidade anterior de cada uma dessas possibilidades seja 0,1, portanto todos esses custos são igualmente prováveis a priori.Então suponha que tomemos apenas 2 amostras de pilha e vemos instruções
I
em ambas as amostras, observação designadao=2/2
. Isso nos fornece novas estimativas da frequênciaf
deI
, de acordo com o seguinte:A última coluna diz que, por exemplo, a probabilidade de que
f
> = 0,5 seja 92%, acima da suposição anterior de 60%.Suponha que as suposições anteriores sejam diferentes. Suponha que assumimos que
P(f=0.1)
seja 0,991 (quase certo) e que todas as outras possibilidades sejam quase impossíveis (0,001). Em outras palavras, nossa certeza anterior é queI
é barato. Então temos:Agora ele diz que
P(f >= 0.5)
é 26%, acima da suposição anterior de 0,6%. Então Bayes nos permite atualizar nossa estimativa do custo provável deI
. Se a quantidade de dados é pequena, não nos diz com precisão qual é o custo, apenas que é grande o suficiente para valer a pena consertar.Ainda outra maneira de olhar para isso é chamada de regra de sucessão . Se você jogar uma moeda 2 vezes e ela aparecer nas duas vezes, o que isso diz sobre o provável peso da moeda? A maneira respeitada de responder é dizer que é uma distribuição Beta, com valor médio
(number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75%
.(A chave é que vemos
I
mais de uma vez. Se a vemos apenas uma vez, isso não nos diz muito, exceto quef
> 0.)Portanto, mesmo um número muito pequeno de amostras pode nos dizer muito sobre o custo das instruções que ele vê. (E ele vai vê-los com uma frequência, em média, proporcional ao seu custo. Se
n
as amostras são colhidas, ef
é o custo, em seguida,I
vai aparecer emnf+/-sqrt(nf(1-f))
amostras. Exemplo,n=10
,f=0.3
, isto é3+/-1.4
amostras.)Adicionado : para dar uma idéia intuitiva da diferença entre medição e amostragem aleatória de pilhas:
agora existem criadores de perfil que fazem a amostragem da pilha, mesmo no horário do relógio de parede, mas o que sai são medições (ou hot path, ou hot spot, dos quais um "gargalo" pode ocultar facilmente). O que eles não mostram (e poderiam facilmente) são as próprias amostras. E se seu objetivo é encontrar o gargalo, o número deles que você precisa ver é, em média , 2 dividido pela fração do tempo que leva. Portanto, se levar 30% do tempo, 2 / .3 = 6,7 amostras, em média, a mostrarão, e a chance de 20 amostras mostrarem é de 99,2%.
Aqui está uma ilustração simplificada da diferença entre examinar medições e examinar amostras de pilhas. O gargalo pode ser um grande blob como esse, ou vários pequenos, não faz diferença.
A medição é horizontal; indica a fração do tempo que as rotinas específicas levam. A amostragem é vertical. Se houver alguma maneira de evitar o que todo o programa está fazendo naquele momento, e se você o vir em uma segunda amostra , encontrou o gargalo. É isso que faz a diferença - ver toda a razão do tempo gasto, não apenas quanto.
fonte
Você pode usar o Valgrind com as seguintes opções
Irá gerar um arquivo chamado
callgrind.out.x
. Você pode usar akcachegrind
ferramenta para ler este arquivo. Ele fornecerá uma análise gráfica de coisas com resultados como quais linhas custam quanto.fonte
./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
gprof2dot
agora está aqui: github.com/jrfonseca/gprof2dotPresumo que você esteja usando o GCC. A solução padrão seria criar um perfil com o gprof .
Certifique-se de adicionar
-pg
à compilação antes de criar um perfil:Ainda não experimentei, mas ouvi coisas boas sobre o google-perftools . Definitivamente vale a pena tentar.
Pergunta relacionada aqui .
Algumas outras palavras-chave se você
gprof
não fizer o trabalho: Valgrind , Intel VTune , Sun DTrace .fonte
Os kernels mais recentes (por exemplo, os kernels mais recentes do Ubuntu) vêm com as novas ferramentas 'perf' (
apt-get install linux-tools
) AKA perf_events .Eles vêm com criadores de perfil de amostragem clássicos ( página de manual ) e também com o incrível timechart !
O importante é que essas ferramentas podem ter perfil de sistema e não apenas perfil de processo - elas podem mostrar a interação entre threads, processos e kernel e permitem entender o agendamento e as dependências de E / S entre processos.
fonte
perf report
parece-me dar os nomes das funções com os pais chamada ... (por isso é uma espécie de visão borboleta invertido)gprof2dot
eperf script
. Ferramenta muito boa!perf
existe em archive.li/9r927#selection-767.126-767.271 (Por que os deuses por isso decidiu excluir a página da base de conhecimento SO está além de mim ....)Eu usaria Valgrind e Callgrind como base para o meu conjunto de ferramentas de criação de perfil. O que é importante saber é que o Valgrind é basicamente uma máquina virtual:
O Callgrind é um construtor de perfil baseado nisso. O principal benefício é que você não precisa executar seu aplicativo por horas para obter resultados confiáveis. Mesmo uma segunda execução é suficiente para obter resultados sólidos e confiáveis, porque o Callgrind é um criador de perfil sem análise.
Outra ferramenta construída sobre Valgrind é o Massif. Eu o uso para criar perfil de uso de memória heap. Isso funciona muito bem. O que ele faz é fornecer instantâneos do uso da memória - informações detalhadas O que contém QUALQUER porcentagem de memória e a OMS a colocou lá. Essas informações estão disponíveis em diferentes momentos da execução do aplicativo.
fonte
A resposta a ser executada
valgrind --tool=callgrind
não é completa sem algumas opções. Normalmente, não queremos criar um perfil de 10 minutos de tempo de inicialização lento no Valgrind e queremos criar um perfil do nosso programa quando ele estiver executando alguma tarefa.Então é isso que eu recomendo. Execute o programa primeiro:
Agora, quando ele funciona e queremos iniciar a criação de perfil, devemos executar em outra janela:
Isso ativa a criação de perfil. Para desativá-lo e interromper toda a tarefa, podemos usar:
Agora, temos alguns arquivos chamados callgrind.out. * No diretório atual. Para ver os resultados da criação de perfil, use:
Eu recomendo na próxima janela clicar no cabeçalho da coluna "Auto", caso contrário, mostra que "main ()" é a tarefa mais demorada. "Self" mostra quanto cada função em si levou tempo, não em conjunto com os dependentes.
fonte
CALLGRIND_TOGGLE_COLLECT
para ativar / desativar a coleção programaticamente; Veja stackoverflow.com/a/13700817/288875Esta é uma resposta à resposta de Nazgob no Gprof .
Uso o Gprof nos últimos dias e já encontrei três limitações significativas, uma das quais ainda não vi documentada em nenhum outro lugar:
Ele não funciona corretamente no código multithread, a menos que você use uma solução alternativa
O gráfico de chamada fica confuso pelos ponteiros de função. Exemplo: Eu tenho uma função chamada
multithread()
que permite multiencadear uma função especificada em uma matriz especificada (ambas passadas como argumentos). O Gprof, no entanto, vê todas as chamadasmultithread()
como equivalentes para fins de computação do tempo gasto em crianças. Como algumas funções passammultithread()
mais tempo do que outras, meus gráficos de chamada são inúteis. (Para aqueles que se perguntam se o encadeamento é o problema aqui: não,multithread()
pode , opcionalmente, e nesse caso, executar tudo sequencialmente apenas no encadeamento de chamada).Diz aqui que "... os números de número de chamadas são obtidos por contagem, não por amostragem. Eles são completamente precisos ...". No entanto, acho que meu gráfico de chamadas me fornece 5345859132 + 784984078 como estatísticas de chamadas para minha função mais chamada, onde o primeiro número deve ser direto e as segundas chamadas recursivas (que são todas por si só). Como isso implicava que eu tinha um bug, coloquei contadores longos (64 bits) no código e fiz a mesma execução novamente. Minhas contas: 5345859132 chamadas diretas e 78094395406 auto-recursivas. Existem muitos dígitos, por isso vou apontar que as chamadas recursivas que medem são 78 bilhões, contra 784m do Gprof: um fator de 100 diferente. Ambas as execuções eram de código único e não otimizado, uma compilada
-g
e outra-pg
.Este foi o GNU Gprof (GNU Binutils para Debian) 2.18.0.20080103 sendo executado no Debian Lenny de 64 bits, se isso ajudar alguém.
fonte
Use Valgrind, callgrind e kcachegrind:
gera callgrind.out.x. Leia-o usando o kcachegrind.
Use o gprof (add -pg):
(não é tão bom para multi-threads, ponteiros de função)
Use o google-perftools:
Usa amostragem de tempo, gargalos de E / S e CPU são revelados.
O Intel VTune é o melhor (gratuito para fins educacionais).
Outros: AMD Codeanalyst (substituído pelo AMD CodeXL), OProfile, ferramentas 'perf' (apt-get install linux-tools)
fonte
Pesquisa de técnicas de criação de perfil em C ++
Nesta resposta, usarei várias ferramentas diferentes para analisar alguns programas de teste muito simples, a fim de comparar concretamente como essas ferramentas funcionam.
O seguinte programa de teste é muito simples e faz o seguinte:
main
chamadasfast
emaybe_slow
3 vezes, uma dasmaybe_slow
chamadas que estão sendo lentaA chamada lenta de
maybe_slow
é 10x mais longa e domina o tempo de execução se considerarmos as chamadas para a função filhocommon
. Idealmente, a ferramenta de criação de perfil poderá apontar para a chamada lenta específica.both
fast
emaybe_slow
callcommon
, que representam a maior parte da execução do programaA interface do programa é:
e o programa faz
O(n^2)
loops no total.seed
é apenas obter uma saída diferente sem afetar o tempo de execução.main.c
gprof
O gprof requer recompilar o software com a instrumentação e também usa uma abordagem de amostragem junto com essa instrumentação. Portanto, ele encontra um equilíbrio entre precisão (a amostragem nem sempre é totalmente precisa e pode pular funções) e desaceleração da execução (instrumentação e amostragem são técnicas relativamente rápidas que não diminuem muito a execução).
O gprof é incorporado ao GCC / binutils, então tudo o que precisamos fazer é compilar com a
-pg
opção de ativar o gprof. Em seguida, executamos o programa normalmente com um parâmetro CLI de tamanho que produz uma duração razoável de alguns segundos (10000
):Por motivos educacionais, também faremos uma corrida sem as otimizações ativadas. Observe que isso é inútil na prática, pois você normalmente se preocupa apenas em otimizar o desempenho do programa otimizado:
Primeiro,
time
nos diz que o tempo de execução com e sem-pg
o mesmo foi, o que é ótimo: sem lentidão! No entanto, vi relatos de desacelerações 2x - 3x em software complexo, por exemplo, como mostrado neste tíquete .Como compilamos
-pg
, a execução do programa produz umgmon.out
arquivo contendo os dados de criação de perfil.Podemos observar graficamente esse arquivo,
gprof2dot
conforme solicitado em: É possível obter uma representação gráfica dos resultados do gprof?Aqui, a
gprof
ferramenta lê asgmon.out
informações de rastreamento e gera um relatório legível por humanosmain.gprof
, que égprof2dot
lido para gerar um gráfico.A fonte do gprof2dot está em: https://github.com/jrfonseca/gprof2dot
Observamos o seguinte para a
-O0
execução:e para a
-O3
corrida:A
-O0
saída é praticamente auto-explicativa. Por exemplo, mostra que as 3maybe_slow
chamadas e as chamadas filho ocupam 97,56% do tempo de execução total, embora a execução demaybe_slow
si mesma sem filhos represente 0,00% do tempo total de execução, ou seja, quase todo o tempo gasto nessa função foi gasto em chamadas de criança.TODO: por que está
main
faltando na-O3
saída, mesmo que eu possa vê-la em umbt
no GDB? Função ausente da saída do GProf Eu acho que é porque o gprof também está baseado em amostragem, além de sua instrumentação compilada, e-O3
main
é muito rápido e não tem amostras.Eu escolho a saída SVG em vez de PNG porque o SVG é pesquisável com Ctrl + F e o tamanho do arquivo pode ser cerca de 10x menor. Além disso, a largura e a altura da imagem gerada podem ser enormes, com dezenas de milhares de pixels para softwares complexos, e o GNOME
eog
3.28.1 aparece nesse caso para PNGs, enquanto os SVGs são abertos automaticamente pelo meu navegador. O gimp 2.8 funcionou bem, veja também:mas, mesmo assim, você arrastará muito a imagem para encontrar o que deseja; veja, por exemplo, esta imagem de um exemplo de software "real" extraído deste ticket :
Você consegue encontrar facilmente a pilha de chamadas mais crítica com todas essas minúsculas linhas de espaguete sem classificação passando uma sobre a outra? Pode haver
dot
opções melhores , tenho certeza, mas não quero ir para lá agora. O que realmente precisamos é de um visualizador dedicado adequado, mas ainda não encontrei um:No entanto, você pode usar o mapa de cores para atenuar um pouco esses problemas. Por exemplo, na enorme imagem anterior, finalmente consegui encontrar o caminho crítico à esquerda quando fiz a dedução brilhante de que o verde vem depois do vermelho, seguido finalmente do azul mais escuro e mais escuro.
Como alternativa, também podemos observar a saída de texto da
gprof
ferramenta binutils incorporada, que salvamos anteriormente em:Por padrão, isso produz uma saída extremamente detalhada que explica o que os dados de saída significam. Como não posso explicar melhor do que isso, vou deixar você ler você mesmo.
Depois de entender o formato de saída de dados, você pode reduzir a verbosidade para mostrar apenas os dados sem o tutorial com a
-b
opção:No nosso exemplo, os resultados foram para
-O0
:e para
-O3
:Como um resumo muito rápido para cada seção, por exemplo:
centra-se na função que é deixada recuada (
maybe_flow
).[3]
é o ID dessa função. Acima da função, estão os chamadores e, abaixo, os callees.Pois
-O3
, veja aqui como na saída gráfica quemaybe_slow
efast
não possui um pai conhecido, que é o que a documentação diz que<spontaneous>
significa.Não tenho certeza se existe uma boa maneira de criar perfil linha por linha com o gprof: tempo `gprof` gasto em linhas de código específicas
valgrind callgrind
O valgrind executa o programa através da máquina virtual valgrind. Isso torna a criação de perfil muito precisa, mas também produz uma desaceleração muito grande do programa. Também mencionei o kcachegrind anteriormente em: Ferramentas para obter um gráfico de chamada de função pictórica do código
callgrind é a ferramenta do valgrind para criar um perfil do código e o kcachegrind é um programa do KDE que pode visualizar a saída do cachegrind.
Primeiro, precisamos remover o
-pg
sinalizador para voltar à compilação normal, caso contrário, a execução falhará comProfiling timer expired
, sim, isso é tão comum que eu fiz e houve uma pergunta de estouro de pilha.Então, compilamos e executamos como:
Eu habilito
--dump-instr=yes --collect-jumps=yes
porque isso também despeja informações que nos permitem exibir uma quebra de desempenho por linha de montagem, a um custo adicional relativamente pequeno.Logo de cara,
time
diz-nos que o programa levou 29,5 segundos para ser executado; portanto, tivemos um abrandamento de cerca de 15x neste exemplo. Claramente, essa desaceleração será uma limitação séria para cargas de trabalho maiores. No "exemplo de software do mundo real" mencionado aqui , observei uma desaceleração de 80x.A execução gera um arquivo de dados de perfil chamado,
callgrind.out.<pid>
por exemplo,callgrind.out.8554
no meu caso. Vemos esse arquivo com:que mostra uma GUI que contém dados semelhantes à saída textual do gprof:
Além disso, se formos na parte inferior direita da guia "Call Graph", vemos um gráfico de chamada que podemos exportar clicando com o botão direito do mouse para obter a seguinte imagem com quantidades irracionais de borda branca :-)
Acho que
fast
não está aparecendo nesse gráfico porque o kcachegrind deve ter simplificado a visualização, porque essa chamada leva muito pouco tempo; esse provavelmente será o comportamento que você deseja em um programa real. O menu do botão direito do mouse possui algumas configurações para controlar quando selecionar esses nós, mas não consegui mostrar uma ligação tão curta depois de uma tentativa rápida. Se eu clicar nafast
janela esquerda, ele mostra um gráfico de chamada comfast
, de modo que a pilha foi realmente capturada. Ninguém ainda havia encontrado uma maneira de mostrar o gráfico de chamada do gráfico completo: Faça com que o callgrind mostre todas as chamadas de função no gráfico de chamada kcachegrindTODO em software C ++ complexo, vejo algumas entradas do tipo
<cycle N>
, por exemplo,<cycle 11>
onde eu esperaria nomes de funções, o que isso significa? Percebi que existe um botão "Detecção de ciclo" para ativar e desativar esse recurso, mas o que isso significa?perf
delinux-tools
perf
parece usar exclusivamente mecanismos de amostragem de kernel do Linux. Isso facilita a configuração, mas também não é totalmente preciso.Isso adicionou 0,2s à execução, por isso estamos bem no tempo, mas ainda não vejo muito interesse depois de expandir o
common
nó com a seta à direita do teclado:Então, eu tento avaliar o
-O0
programa para ver se isso mostra alguma coisa, e só agora, finalmente, vejo um gráfico de chamada:TODO: o que aconteceu na
-O3
execução? É simplesmente issomaybe_slow
efast
foi rápido demais e não obteve nenhuma amostra? Funciona bem-O3
em programas maiores que demoram mais para serem executados? Perdi alguma opção de CLI? Eu descobri que estava prestes-F
a controlar a frequência da amostra no Hertz, mas-F 39500
aumentei para o máximo permitido por padrão de (poderia ser aumentado comsudo
) e ainda não vejo chamadas claras.Uma coisa interessante
perf
é a ferramenta FlameGraph de Brendan Gregg, que exibe os tempos da pilha de chamadas de uma maneira bem organizada que permite ver rapidamente as grandes chamadas. A ferramenta está disponível em: https://github.com/brendangregg/FlameGraph e também é mencionado em sua perf tutorial em: http://www.brendangregg.com/perf.html#FlameGraphs Quando eu corriperf
semsudo
chegueiERROR: No stack counts found
então para agora eu vou fazer isso comsudo
:mas em um programa tão simples a saída não é muito fácil de entender, pois não podemos ver nem
maybe_slow
nem facilmentefast
esse gráfico:No exemplo mais complexo, fica claro o significado do gráfico:
TODO, há um log de
[unknown]
funções nesse exemplo, por que isso?Outras interfaces GU perf que podem valer a pena incluem:
Plug-in do Eclipse Trace Compass: https://www.eclipse.org/tracecompass/
Mas isso tem a desvantagem de que você precisa primeiro converter os dados para o Common Trace Format, o que pode ser feito
perf data --to-ctf
, mas precisa ser ativado no tempo de compilação / terperf
novo o suficiente, um dos quais não é o caso do perf Ubuntu 18.04https://github.com/KDAB/hotspot
A desvantagem disso é que parece não haver um pacote Ubuntu, e sua construção requer o Qt 5.10 enquanto o Ubuntu 18.04 está no Qt 5.9.
gperftools
Anteriormente chamado de "Ferramentas de desempenho do Google", fonte: https://github.com/gperftools/gperftools Baseado em amostra.
Primeiro instale o gperftools com:
Em seguida, podemos ativar o perfilador de CPU gperftools de duas maneiras: em tempo de execução ou em tempo de construção.
Em tempo de execução, precisamos passar o conjunto
LD_PRELOAD
para apontar para olibprofiler.so
qual você pode encontrarlocate libprofiler.so
, por exemplo, no meu sistema:Como alternativa, podemos construir a biblioteca no tempo do link, dispensando a passagem
LD_PRELOAD
no tempo de execução:Veja também: gperftools - arquivo de perfil não despejado
A maneira mais agradável de visualizar esses dados que encontrei até agora é tornar a saída do pprof no mesmo formato que o kcachegrind usa como entrada (sim, a ferramenta Valgrind-project-viewer-tool) e usar o kcachegrind para visualizar o seguinte:
Após executar com qualquer um desses métodos, obtemos um
prof.out
arquivo de dados de perfil como saída. Podemos visualizar esse arquivo graficamente como um SVG com:que fornece um gráfico de chamada familiar como outras ferramentas, mas com a unidade desajeitada do número de amostras em vez de segundos.
Como alternativa, também podemos obter alguns dados textuais com:
que dá:
Veja também: Como usar as ferramentas do google perf
Testado no Ubuntu 18.04, gprof2dot 2019.11.30, valgrind 3.13.0, perf 4.15.18, kernel Linux 4.15.0, FLameGraph 1a0dc6985aad06e76857cf2a354bd5ba0c9ce96b, gperftools 2.5-2.
fonte
-fno-omit-frame-pointer
sinalizador ou usar uma alternativa diferente: grave com--call-graph "dwarf"
ou--call-graph "lbr"
dependendo do seu cenário.Para programas de thread único, você pode usar o igprof , The Ignominous Profiler: https://igprof.org/ .
É um perfilador de amostragem, seguindo as linhas da ... resposta longa ... de Mike Dunlavey, que embrulhará os resultados em uma árvore de pilha de chamadas navegável, anotada com o tempo ou a memória gasta em cada função, cumulativa ou por função.
fonte
Também vale a pena mencionar são
Eu usei o HPCToolkit e o VTune e eles são muito eficazes para encontrar o pólo longo na barraca e não precisam que seu código seja recompilado (exceto que você precisa usar a construção do tipo -g -O ou RelWithDebInfo no CMake para obter uma saída significativa) . Ouvi dizer que a TAU é semelhante em recursos.
fonte
Estes são os dois métodos que eu uso para acelerar meu código:
Para aplicativos vinculados à CPU:
Para aplicativos de ligação de E / S:
NB
Se você não possui um perfilador, use o perfil do pobre homem. Clique em pausa enquanto depura seu aplicativo. A maioria dos pacotes de desenvolvedores entra em montagem com números de linhas comentados. Você tem probabilidade estatisticamente de pousar em uma região que está consumindo a maior parte dos ciclos de sua CPU.
Para a CPU, o motivo da criação de perfil no modo DEBUG é que, se você tentar criar um perfil no modo RELEASE , o compilador reduzirá a matemática, vetorizar loops e funções em linha que tendem a transformar seu código em uma bagunça não mapeada quando montado. Uma bagunça não mapeável significa que seu criador de perfil não será capaz de identificar claramente o que está demorando tanto, porque o assembly pode não corresponder ao código-fonte em otimização . Se você precisar do desempenho (por exemplo, sensível ao tempo) do modo RELEASE , desative os recursos do depurador conforme necessário para manter um desempenho utilizável.
Para o limite de E / S, o criador de perfil ainda pode identificar operações de E / S no modo RELEASE , porque as operações de E / S são vinculadas externamente a uma biblioteca compartilhada (na maioria das vezes) ou, na pior das hipóteses, resultam em um sistema. vetor de interrupção de chamada (que também é facilmente identificável pelo criador de perfil).
fonte
Você pode usar a biblioteca iprof:
https://gitlab.com/Neurochrom/iprof
https://github.com/Neurochrom/iprof
É multiplataforma e permite que você não avalie o desempenho do seu aplicativo também em tempo real. Você pode até combiná-lo com um gráfico ao vivo. Isenção total: Eu sou o autor.
fonte
Você pode usar uma estrutura de log como
loguru
uma vez que inclui registros de data e hora e tempo de atividade total que podem ser usados com bom gosto para criação de perfil:fonte
No trabalho, temos uma ferramenta muito boa que nos ajuda a monitorar o que queremos em termos de agendamento. Isso tem sido útil várias vezes.
Está em C ++ e deve ser personalizado de acordo com suas necessidades. Infelizmente não posso compartilhar código, apenas conceitos. Você usa um
volatile
buffer "grande" que contém carimbos de data e hora e ID do evento que pode despejar post mortem ou depois de parar o sistema de log (e despejar isso em um arquivo, por exemplo).Você recupera o chamado buffer grande com todos os dados e uma pequena interface o analisa e mostra eventos com nome (para cima / para baixo + valor), como um osciloscópio faz com as cores (configuradas no
.hpp
arquivo).Você personaliza a quantidade de eventos gerados para se concentrar apenas no que deseja. Isso nos ajudou muito a agendar problemas e consumir a quantidade de CPU que desejávamos, com base na quantidade de eventos registrados por segundo.
Você precisa de 3 arquivos:
O conceito é definir eventos
tool_events_id.hpp
assim:Você também define algumas funções em
toolname.hpp
:Onde quer que você codifique, você pode usar:
A
probe
função usa algumas linhas de montagem para recuperar o registro de data e hora do relógio o mais rápido possível e, em seguida, define uma entrada no buffer. Também temos um incremento atômico para encontrar com segurança um índice onde armazenar o evento de log. Claro que o buffer é circular.Espero que a idéia não seja ofuscada pela falta de código de exemplo.
fonte
Na verdade, um pouco surpreso, não muitos mencionados sobre o google / benchmark , embora seja um pouco complicado fixar a área específica do código, especialmente se a base de código for um pouco grande, no entanto, achei isso realmente útil quando usado em combinação com
callgrind
IMHO identificar a peça que está causando gargalo é a chave aqui. No entanto, eu tentaria responder às perguntas a seguir primeiro e escolher a ferramenta com base nisso
valgrind
com a combinação decallrind
ekcachegrind
deve fornecer uma estimativa decente dos pontos acima e, uma vez estabelecido que há problemas com alguma seção do código, eu sugiro que uma micro benchmarkgoogle benchmark
seja um bom ponto de partida.fonte
Use o
-pg
sinalizador ao compilar e vincular o código e execute o arquivo executável. Enquanto esse programa é executado, os dados de criação de perfil são coletados no arquivo a.out.Existem dois tipos diferentes de criação de perfil
1- Criação de perfil simples:
executando o comando,
gprog --flat-profile a.out
você obtém os seguintes dados- qual a porcentagem do tempo total gasto para a função,
- quantos segundos foram gastos em uma função - incluindo e excluindo chamadas para subfunções,
- o número de chamadas,
- o tempo médio por chamada.
2- o gráfico mostra
o comando
gprof --graph a.out
para obter os seguintes dados para cada função que inclui- Em cada seção, uma função é marcada com um número de índice.
- Função acima, há uma lista de funções que chamam a função.
- Abaixo da função, há uma lista de funções que são chamadas pela função.
Para obter mais informações, consulte https://sourceware.org/binutils/docs-2.32/gprof/
fonte
Como ninguém mencionou o Arm MAP, eu o adicionaria pessoalmente. Utilizei o Map com sucesso para criar um perfil de um programa científico em C ++.
O Arm MAP é o criador de perfil para códigos C, C ++, Fortran e F90 paralelos, multithread ou single threaded. Ele fornece análises detalhadas e identificação de gargalos na linha de origem. Diferentemente da maioria dos criadores de perfil, ele foi projetado para poder criar perfis de pthreads, OpenMP ou MPI para código paralelo e encadeado.
MAP é um software comercial.
fonte
usar um software de depuração como identificar onde o código está sendo executado lentamente?
basta pensar que você tem um obstáculo enquanto está em movimento, isso diminuirá sua velocidade
como as operações de loop, realocação indesejada de buffer de realocação, pesquisa, vazamento de memória etc. consomem mais poder de execução, afetando negativamente o desempenho do código. Certifique-se de adicionar -pg à compilação antes de criar um perfil:
g++ your_prg.cpp -pg
oucc my_program.cpp -g -pg
conforme seu compiladorainda não tentei, mas ouvi coisas boas sobre o google-perftools. Definitivamente vale a pena tentar.
valgrind --tool=callgrind ./(Your binary)
Ele irá gerar um arquivo chamado gmon.out ou callgrind.out.x. Você pode usar a ferramenta kcachegrind ou depurador para ler este arquivo. Ele fornecerá uma análise gráfica de coisas com resultados como quais linhas custam quanto.
acho que sim
fonte