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?


27
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?
Nazgob 17/12/08

2
Já foi respondido no seguinte link: stackoverflow.com/questions/2497211/…
Kapil Gupta

4
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.
ruído artless


3
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

  1. Eles não resumem no nível da instrução e
  2. 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.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 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.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 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.

insira a descrição da imagem aqui

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.


292
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.
precisa

583

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

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

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.


51
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.
Entusiasticgeek #

1
stackoverflow.com/questions/375913/… é solução parcial para problemas de velocidade.
Samuel Samuel

3
@Sebastian: gprof2dotagora está aqui: github.com/jrfonseca/gprof2dot
John Zwinck

348

Presumo 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:

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

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ê gprofnão fizer o trabalho: Valgrind , Intel VTune , Sun DTrace .


3
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
dario minonne 18/12/2008

7
@ Bill-the-Lizard: Alguns comentários sobre gprof : stackoverflow.com/questions/1777556/alternatives-to-gprof/...
Mike Dunlavey

6
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 .

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.

texto alternativo


12
Ó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!
dashesy

2
Até os kernels mais recentes, como o 4.13, têm o eBPF para criação de perfil. Veja brendangregg.com/blog/2015-05-15/ebpf-one-small-step.html e brendangregg.com/ebpf.html
Andrew Stern

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.


70

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:

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

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.


9
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.
Code Abominator

2
há também CALLGRIND_TOGGLE_COLLECTpara ativar / desativar a coleção programaticamente; Veja stackoverflow.com/a/13700817/288875
Andre Holzner

1
Uau, eu não sabia que isso existia, obrigado!
Vincent Fourmond

59

Esta é 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:

  1. Ele não funciona corretamente no código multithread, a menos que você use uma solução alternativa

  2. 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).

  3. 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).

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


11

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:

  • 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):

gcc -pg -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time ./main.out 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:

gcc -pg -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out 10000

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.

Podemos observar graficamente esse arquivo, gprof2dotconforme solicitado em: É possível obter uma representação gráfica dos resultados do gprof?

sudo apt install graphviz
python3 -m pip install --user gprof2dot
gprof main.out > main.gprof
gprof2dot < main.gprof | dot -Tsvg -o output.svg

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 fonte do gprof2dot está em: https://github.com/jrfonseca/gprof2dot

Observamos o seguinte para a -O0execução:

insira a descrição da imagem aqui

e para a -O3corrida:

insira a descrição da imagem aqui

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 -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 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 :

insira a descrição da imagem aqui

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.35      3.67     3.67   123003     0.00     0.00  common
  0.00      3.67     0.00        3     0.00     0.03  fast
  0.00      3.67     0.00        3     0.00     1.19  maybe_slow

            Call graph


granularity: each sample hit covers 2 byte(s) for 0.27% of 3.67 seconds

index % time    self  children    called     name
                0.09    0.00    3003/123003      fast [4]
                3.58    0.00  120000/123003      maybe_slow [3]
[1]    100.0    3.67    0.00  123003         common [1]
-----------------------------------------------
                                                 <spontaneous>
[2]    100.0    0.00    3.67                 main [2]
                0.00    3.58       3/3           maybe_slow [3]
                0.00    0.09       3/3           fast [4]
-----------------------------------------------
                0.00    3.58       3/3           main [2]
[3]     97.6    0.00    3.58       3         maybe_slow [3]
                3.58    0.00  120000/123003      common [1]
-----------------------------------------------
                0.00    0.09       3/3           main [2]
[4]      2.4    0.00    0.09       3         fast [4]
                0.09    0.00    3003/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.52      1.84     1.84   123003    14.96    14.96  common

            Call graph


granularity: each sample hit covers 2 byte(s) for 0.54% of 1.84 seconds

index % time    self  children    called     name
                0.04    0.00    3003/123003      fast [3]
                1.79    0.00  120000/123003      maybe_slow [2]
[1]    100.0    1.84    0.00  123003         common [1]
-----------------------------------------------
                                                 <spontaneous>
[2]     97.6    0.00    1.79                 maybe_slow [2]
                1.79    0.00  120000/123003      common [1]
-----------------------------------------------
                                                 <spontaneous>
[3]      2.4    0.00    0.04                 fast [3]
                0.04    0.00    3003/123003      common [1]
-----------------------------------------------

Index by function name

   [1] common

Como um resumo muito rápido para cada seção, por exemplo:

                0.00    3.58       3/3           main [2]
[3]     97.6    0.00    3.58       3         maybe_slow [3]
                3.58    0.00  120000/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.

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 -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.

Então, compilamos e executamos como:

sudo apt install kcachegrind valgrind
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time valgrind --tool=callgrind valgrind --dump-instr=yes \
  --collect-jumps=yes ./main.out 10000

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:

insira a descrição da imagem aqui

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 :-)

insira a descrição da imagem aqui

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:

Samples: 7K of event 'cycles:uppp', Event count (approx.): 6228527608     
  Children      Self  Command   Shared Object     Symbol                  
-   99.98%    99.88%  main.out  main.out          [.] common              
     common                                                               
     0.11%     0.11%  main.out  [kernel]          [k] 0xffffffff8a6009e7  
     0.01%     0.01%  main.out  [kernel]          [k] 0xffffffff8a600158  
     0.01%     0.00%  main.out  [unknown]         [k] 0x0000000000000040  
     0.01%     0.00%  main.out  ld-2.27.so        [.] _dl_sysdep_start    
     0.01%     0.00%  main.out  ld-2.27.so        [.] dl_main             
     0.01%     0.00%  main.out  ld-2.27.so        [.] mprotect            
     0.01%     0.00%  main.out  ld-2.27.so        [.] _dl_map_object      
     0.01%     0.00%  main.out  ld-2.27.so        [.] _xstat              
     0.00%     0.00%  main.out  ld-2.27.so        [.] __GI___tunables_init
     0.00%     0.00%  main.out  [unknown]         [.] 0x2f3d4f4944555453  
     0.00%     0.00%  main.out  [unknown]         [.] 0x00007fff3cfc57ac  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _start              

Então, eu tento avaliar o -O0programa para ver se isso mostra alguma coisa, e só agora, finalmente, vejo um gráfico de chamada:

Samples: 15K of event 'cycles:uppp', Event count (approx.): 12438962281   
  Children      Self  Command   Shared Object     Symbol                  
+   99.99%     0.00%  main.out  [unknown]         [.] 0x04be258d4c544155  
+   99.99%     0.00%  main.out  libc-2.27.so      [.] __libc_start_main   
-   99.99%     0.00%  main.out  main.out          [.] main                
   - main                                                                 
      - 97.54% maybe_slow                                                 
           common                                                         
      - 2.45% fast                                                        
           common                                                         
+   99.96%    99.85%  main.out  main.out          [.] common              
+   97.54%     0.03%  main.out  main.out          [.] maybe_slow          
+    2.45%     0.00%  main.out  main.out          [.] fast                
     0.11%     0.11%  main.out  [kernel]          [k] 0xffffffff8a6009e7  
     0.00%     0.00%  main.out  [unknown]         [k] 0x0000000000000040  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_sysdep_start    
     0.00%     0.00%  main.out  ld-2.27.so        [.] dl_main             
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_lookup_symbol_x 
     0.00%     0.00%  main.out  [kernel]          [k] 0xffffffff8a600158  
     0.00%     0.00%  main.out  ld-2.27.so        [.] mmap64              
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_map_object      
     0.00%     0.00%  main.out  ld-2.27.so        [.] __GI___tunables_init
     0.00%     0.00%  main.out  [unknown]         [.] 0x552e53555f6e653d  
     0.00%     0.00%  main.out  [unknown]         [.] 0x00007ffe1cf20fdb  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _start              

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.

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 corri perfsem sudocheguei ERROR: No stack counts foundentão para agora eu vou fazer isso com sudo:

git clone https://github.com/brendangregg/FlameGraph
sudo perf record -F 99 -g -o perf_with_stack.data ./main.out 10000
sudo perf script -i perf_with_stack.data | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg

mas em um programa tão simples a saída não é muito fácil de entender, pois não podemos ver nem maybe_slownem facilmente fastesse gráfico:

insira a descrição da imagem aqui

No exemplo mais complexo, fica claro o significado do gráfico:

insira a descrição da imagem aqui

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 / ter perfnovo o suficiente, um dos quais não é o caso do perf Ubuntu 18.04

  • https://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:

sudo apt install google-perftools

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:

gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libprofiler.so \
  CPUPROFILE=prof.out ./main.out 10000

Como alternativa, podemos construir a biblioteca no tempo do link, dispensando a passagem LD_PRELOADno tempo de execução:

gcc -Wl,--no-as-needed,-lprofiler,--as-needed -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
CPUPROFILE=prof.out ./main.out 10000

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:

google-pprof --callgrind main.out prof.out  > callgrind.out
kcachegrind callgrind.out

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

insira a descrição da imagem aqui

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
     187 100.0% 100.0%      187 100.0% common
       0   0.0% 100.0%      187 100.0% __libc_start_main
       0   0.0% 100.0%      187 100.0% _start
       0   0.0% 100.0%        4   2.1% fast
       0   0.0% 100.0%      187 100.0% main
       0   0.0% 100.0%      183  97.9% maybe_slow

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.


2
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

  1. 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
  2. Intel VTune ( https://software.intel.com/en-us/vtune ) - Se você possui compiladores Intel, isso é muito bom
  3. TAU ( http://www.cs.uoregon.edu/research/tau/home.php )

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.


4

Estes são os dois métodos que eu uso para acelerar meu código:

Para aplicativos vinculados à CPU:

  1. Use um profiler no modo DEBUG para identificar partes questionáveis ​​do seu código
  2. 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:

  1. 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).


2
+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.
seo


2

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:


1

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:

// EVENT_NAME                         ID      BEGIN_END BG_COLOR NAME
#define SOCK_PDU_RECV_D               0x0301  //@D00301 BGEEAAAA # TX_PDU_Recv
#define SOCK_PDU_RECV_F               0x0302  //@F00301 BGEEAAAA # TX_PDU_Recv

Você também define algumas funções em toolname.hpp:

#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_WARN 1
// ...

void init(void);
void probe(id,payload);
// etc

Onde quer que você codifique, você pode usar:

toolname<LOG_LEVEL>::log(EVENT_NAME,VALUE);

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.


1

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

  1. meu algoritmo está correto?
  2. existem bloqueios que provam ser gargalos?
  3. existe uma seção específica de código que está provando ser a culpada?
  4. 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.


1

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.

Para obter mais informações, consulte https://sourceware.org/binutils/docs-2.32/gprof/


0

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.


0

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.

acho que sim

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.