para - dev c++ linux




Como posso fazer o perfil do código C++ em execução no Linux? (8)

Eu tenho um aplicativo C ++, em execução no Linux, que estou no processo de otimização. Como posso identificar quais áreas do meu código estão sendo executadas lentamente?


A resposta para executar valgrind --tool=callgrind não está completa, sem algumas opções. Normalmente, não desejamos criar um perfil de 10 minutos de tempo de inicialização lento em Valgrind e queremos criar um perfil de nosso programa quando ele estiver realizando alguma tarefa.

Então é isso que eu recomendo. Execute o programa primeiro:

valgrind --tool=callgrind --dump-instr=yes -v --instr-atstart=no ./binary > tmp

Agora, quando funciona e queremos iniciar o perfil, devemos executar em outra janela:

callgrind_control -i on

Isso ativa a criação de perfis. Para desligá-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 de criação de perfil, use:

kcachegrind callgrind.out.*

Eu recomendo na próxima janela para clicar no cabeçalho da coluna "Self", caso contrário, mostra que "main ()" é tarefa mais demorada. "Self" mostra quanto cada função levou tempo, não junto com dependentes.


Esta é uma resposta à resposta do Gprof da Nazgob .

Eu tenho usado o Gprof nos últimos dias e já encontrei três limitações significativas, uma das quais eu não vi documentadas em nenhum outro lugar (ainda):

  1. Ele não funciona corretamente no código multi-threaded, a menos que você use uma workaround

  2. O gráfico de chamadas fica confuso pelos ponteiros de função. Exemplo: Eu tenho uma função chamada multithread() que me permite multi-thread uma função especificada sobre uma matriz especificada (ambos passados ​​como argumentos). No entanto, o Gprof considera todas as chamadas para multithread() como equivalentes para fins de cálculo do tempo gasto em crianças. Como algumas funções que passo para multithread() demoram muito mais que outras, meus gráficos de chamada são praticamente inúteis. (Para aqueles perguntando se threading é o problema aqui: não, multithread() pode, opcionalmente, e fez neste caso, executar tudo seqüencialmente no segmento de chamada apenas).

  3. Aqui diz que "... os números de número de chamadas são derivados por contagem, não por amostragem. Eles são completamente precisos ...". No entanto, eu acho meu gráfico de chamada me dando 5345859132 + 784984078 como estatísticas de chamada para a minha função mais chamada, onde o primeiro número deve ser chamadas diretas, e as segundas chamadas recursivas (que são todas de si mesmo). Como isso implicava que eu tinha um bug, coloquei contadores longos (64 bits) no código e fiz a mesma execução novamente. Minhas contagens: 5345859132 direta e 78094395406 chamadas auto-recursivas. Há muitos dígitos lá, então vou apontar que as chamadas recursivas que eu menso são 78 bilhões, contra 784 milhões do Gprof: um fator de 100 diferente. Ambas as execuções eram código único e não-otimizado, um compilado -g e o outro -pg .

Este foi GNU Gprof (GNU Binutils para Debian) 2.18.0.20080103 rodando sob o Debian Lenny de 64 bits, se isso ajudar alguém.


Eu suponho que você esteja usando o GCC. A solução padrão seria o perfil com gprof .

Certifique-se de adicionar -pg à compilação antes de -pg perfil:

cc -o myprog myprog.c utils.c -g -pg

Eu não tentei ainda, mas ouvi coisas boas sobre o google-perftools . É definitivamente vale a pena tentar.

Questão relacionada here .

Alguns outros chavões se o gprof não fizer o trabalho para você: Valgrind , Intel VTune , Sun DTrace .


Eu usaria o Valgrind e o Callgrind como base para minha suíte de ferramentas de criação de perfil. O que é importante saber é que Valgrind é basicamente uma máquina virtual:

(wikipedia) 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, Valgrind primeiro converte o programa em um formato mais simples e temporário chamado de Representação Intermediária (IR), que é um formulário baseado em SSA e neutro do processador. Após a conversão, uma ferramenta (veja abaixo) é livre para fazer qualquer transformação que desejar no IR, antes que Valgrind converta o IR de volta no código de máquina e deixe o processador do host executá-lo.

Callgrind é um profiler construído sobre isso. O principal benefício é que você não precisa executar sua aplicação por horas para obter resultados confiáveis. Até mesmo uma segunda corrida é suficiente para obter resultados confiáveis ​​e sólidos, porque o Callgrind é um gerenciador de perfis sem necessidade de testes.

Outra ferramenta construída sobre Valgrind é o Massif. Eu usá-lo para o perfil de uso de memória heap. Isso funciona muito bem. O que ele faz é que ele mostra instantâneos do uso da memória - informações detalhadas sobre qual porcentagem de memória ea WHO colocou lá. Essas informações estão disponíveis em diferentes momentos da execução do aplicativo.


Para programas single-threaded você pode usar o igprof , The Ignominous Profiler: https://igprof.org/ .

É um profiler de amostragem, ao longo das linhas da ... longa ... resposta por Mike Dunlavey, que irá embrulhar os resultados em uma árvore de pilha de chamada navegável, anotada com o tempo ou a memória gasto em cada função, seja cumulativa ou por função.


Se seu objetivo é usar um profiler, use um dos sugeridos.

No entanto, se você estiver com pressa e puder interromper manualmente seu programa no depurador enquanto ele estiver sendo subjetivamente lento, há uma maneira simples de encontrar problemas de desempenho.

Basta interrompê-lo várias vezes e, a cada vez, observar a pilha de chamadas. Se houver algum código que está perdendo uma porcentagem do tempo, 20% ou 50% ou o que quer que seja, é a probabilidade de você pegá-lo no ato em cada amostra. Então, essa é aproximadamente a porcentagem de amostras nas quais você a verá. Não há adivinhações necessárias. Se você adivinhar qual é o problema, isso provará ou refutará isso.

Você pode ter vários problemas de desempenho de tamanhos diferentes. Se você limpar qualquer um deles, os restantes terão uma porcentagem maior e serão mais fáceis de detectar nos passes subseqüentes. Esse efeito de ampliação , quando combinado com vários problemas, pode levar a fatores de aceleração verdadeiramente massivos.

Advertência: Os programadores tendem a ser céticos quanto a essa técnica, a menos que eles mesmos a tenham usado. Eles dirão que os profilers fornecem a você essa informação, mas isso só é verdade se eles experimentarem toda a pilha de chamadas e, em seguida, permitirem que você examine um conjunto aleatório de amostras. (Os resumos são onde o insight é perdido.) Gráficos de chamadas não fornecem as mesmas informações, porque

  1. eles não resumem no nível de instrução, e
  2. eles dão resumos confusos na presença de recursão.

Eles também dizem que só funciona em programas de brinquedos, quando na verdade funciona em qualquer programa, e parece funcionar melhor em programas maiores, porque eles tendem a ter mais problemas para encontrar. Eles vão dizer 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, isso é real.

PS Isso também pode ser feito em programas multi-thread, se houver uma maneira de coletar amostras de pilha de chamadas do conjunto de encadeamentos em um ponto no tempo, como existe em Java.

PPS Como uma generalidade grosseira, quanto mais camadas de abstração você tiver em seu software, maior será a probabilidade de descobrir que essa é a causa de problemas de desempenho (e a oportunidade de aumentar a velocidade).

Adicionado: Pode não ser óbvio, mas a técnica de amostragem de pilha funciona igualmente bem na presença de recursão. A razão é que o tempo que seria salvo pela remoção de uma instrução é aproximado pela fração de amostras que a contém, independentemente do número de vezes que pode ocorrer dentro de uma amostra.

Outra objeção que eu ouço com frequência é: " Parará em algum lugar aleatório e perderá o verdadeiro problema ". Isto vem de ter um conceito prévio do que é o problema real. Uma propriedade chave dos problemas de desempenho é que eles desafiam as expectativas. A amostragem diz que algo é um problema e sua primeira reação é descrença. Isso é natural, mas você pode ter certeza se encontrar um problema é real, e vice-versa.

ADICIONADO: Deixe-me fazer uma explicação bayesiana de como isso funciona. Suponha que haja alguma instrução I (chamada ou não) que esteja na pilha de chamadas, uma fração f do tempo (e, portanto, custa muito). Para simplificar, 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 é 0,1, então todos esses custos são igualmente provavelmente a-priori.

Então suponha que tenhamos apenas 2 amostras de pilha, e vemos a instrução I em ambas as amostras, designada observação o=2/2 . Isso nos dá novas estimativas da freqüência f de I , de acordo com isso:

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)

0.1    1     1             0.1          0.1            0.25974026
0.1    0.9   0.81          0.081        0.181          0.47012987
0.1    0.8   0.64          0.064        0.245          0.636363636
0.1    0.7   0.49          0.049        0.294          0.763636364
0.1    0.6   0.36          0.036        0.33           0.857142857
0.1    0.5   0.25          0.025        0.355          0.922077922
0.1    0.4   0.16          0.016        0.371          0.963636364
0.1    0.3   0.09          0.009        0.38           0.987012987
0.1    0.2   0.04          0.004        0.384          0.997402597
0.1    0.1   0.01          0.001        0.385          1

                  P(o=2/2) 0.385                

A última coluna diz que, por exemplo, a probabilidade de que f > = 0,5 é de 92%, acima da suposição anterior de 60%.

Suponha que as suposições anteriores sejam diferentes. Suponha que assumimos P (f = 0,1) é 0,991 (quase certo), e todas as outras possibilidades são quase impossíveis (0,001). Em outras palavras, nossa certeza anterior é que I sou barato. Então nós 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)

0.001  1    1              0.001        0.001          0.072727273
0.001  0.9  0.81           0.00081      0.00181        0.131636364
0.001  0.8  0.64           0.00064      0.00245        0.178181818
0.001  0.7  0.49           0.00049      0.00294        0.213818182
0.001  0.6  0.36           0.00036      0.0033         0.24
0.001  0.5  0.25           0.00025      0.00355        0.258181818
0.001  0.4  0.16           0.00016      0.00371        0.269818182
0.001  0.3  0.09           0.00009      0.0038         0.276363636
0.001  0.2  0.04           0.00004      0.00384        0.279272727
0.991  0.1  0.01           0.00991      0.01375        1

                  P(o=2/2) 0.01375                

Agora, diz que P (f> = 0.5) é 26%, acima do pressuposto anterior de 0.6%. Então Bayes nos permite atualizar nossa estimativa do custo provável de I . Se a quantidade de dados é pequena, ela não nos diz exatamente qual é o custo, apenas que é grande o suficiente para valer a pena ser corrigido.

Ainda outro modo de olhar para isto é chamado a Regra de Sucessão . Se você virar uma moeda duas vezes, e ela aparecer cara ambas as vezes, o que isso lhe diz sobre a provável ponderação da moeda? A maneira respeitada de responder é dizer que é uma distribuição Beta, com valor médio (número de acertos + 1) / (número de tentativas + 2) = (2 + 1) / (2 + 2) = 75%.

(A chave é que a vemos mais de uma vez. Se a vemos apenas uma vez, isso não nos diz muito, exceto que f > 0).

Assim, mesmo um número muito pequeno de amostras pode nos dizer muito sobre o custo das instruções que ele vê. (E ele os verá com uma freqüência, em média, proporcional ao seu custo. Se n amostras forem obtidas, e f é o custo, então I vou aparecer em amostras nf+/-sqrt(nf(1-f)) . , n=10 , f=0.3 , ou seja, 3+/-1.4 amostras.)

ADICIONADO, para dar uma ideia intuitiva da diferença entre a amostragem de medição e de pilha aleatória:
Existem criadores de perfil agora que amostram a pilha, mesmo na hora do relógio de parede, mas o que sai são medições (ou caminho quente, ou ponto quente, a partir do qual um "gargalo" pode ser facilmente ocultado). O que eles não mostram (e podem 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 de tempo que leva. Portanto, se levar 30% do tempo, 2 / .3 = 6,7 amostras, em média, mostrarão, e a chance de 20 amostras mostrarem é de 99,2%.

Aqui está uma ilustração improvisada da diferença entre examinar as medidas e examinar as amostras da pilha. O gargalo pode ser uma grande bolha como esta, ou numerosas pequenas, não faz diferença.

A medição é horizontal; Ele informa a fração de 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ê vê-lo em uma segunda amostra , você encontrou o gargalo. É isso que faz a diferença - ver toda a razão do tempo gasto, não apenas quanto.


Você pode usar o Valgrind com as seguintes opções

valgrind --tool=callgrind ./(Your binary)

Ele irá gerar um arquivo chamado callgrind.out.x . Você pode então usar a ferramenta kcachegrind para ler este arquivo. Ele vai te dar uma análise gráfica de coisas com resultados como quais linhas custam quanto.


Use Valgrind, callgrind e kcachegrind:

valgrind --tool=callgrind ./(Your binary)

gera callgrind.out.x. Leia-o usando o kcachegrind.

Use 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 I / O e CPU são revelados.

O Intel VTune é o melhor (gratuito para fins educacionais).

Outros: AMD Codeanalyst (desde que substituído por AMD CodeXL), OProfile, ferramentas 'perf' (apt-get install linux-tools)





profiling