Alternativas ao gprof [fechado]


166

Que outros programas fazem a mesma coisa que o gprof?


2
em quais plataformas você está interessado?
Osgx

2
Estou interessado em Linux.
neuromancer


13
@Gregory - Estou inclinado a concordar, e talvez ele deve contribuir com respostas de sua autoria, 229 vs 6, todos os 6 dessas respostas sendo a suas próprias perguntas ...
Jean-Bernard Pellerin

5
Como essa pergunta pode não ser construtiva?
JohnTortugo

Respostas:


73

O Valgrind possui um perfilador de contagem de instruções com um visualizador muito bom chamado KCacheGrind . Como Mike Dunlavey recomenda, Valgrind conta a fração de instruções para as quais um procedimento está ativo na pilha, embora lamento dizer que parece estar confuso na presença de recursão mútua. Mas o visualizador é muito bom e tem anos-luz à frente gprof.


2
@ Norman: ++ Essa confusão sobre recursão parece endêmica para sistemas que têm o conceito de propagar tempos entre nós em um gráfico. Também acho que o tempo do relógio de parede geralmente é mais útil que o tempo de instrução da CPU, e as linhas de código (instruções de chamada) são mais úteis que os procedimentos. Se forem coletadas amostras de pilha em horários aleatórios, o custo fracionário de uma linha (ou procedimento ou qualquer outra descrição que você possa fazer) é simplesmente estimado pela fração de amostras que a exibem.
21118 Mike Dunlavey

1
... Estou enfatizando as instruções de chamada, mas isso se aplica a todas as instruções. Se houver um gargalo de ponto de acesso honesto, como um tipo de bolha de uma grande variedade de números, as instruções de comparação / salto / troca / incremento do loop interno estarão na parte superior / inferior de quase todas as amostras de pilha . Mas (especialmente quando o software cresce e quase nenhuma rotina tem muito tempo "próprio"), na verdade, muitos problemas são instruções de chamada, solicitando um trabalho que, quando fica claro quanto custa, realmente não precisa ser feito.
9139 Mike Dunlavey

3
... Veja isso. Eu acho que eles são quase no caminho certo: rotateright.com/zoom.html
Mike Dunlavey

195

O gprof (leia o artigo) existe por razões históricas. Se você acha que isso o ajudará a encontrar problemas de desempenho, nunca foi anunciado como tal. Aqui está o que o artigo diz:

O perfil pode ser usado para comparar e avaliar os custos de várias implementações.

Não diz que pode ser usado para identificar as várias implementações a serem avaliadas, embora implique que poderia, em circunstâncias especiais:

especialmente se pequenas partes do programa dominam seu tempo de execução.

E os problemas que não são tão localizados? Isso não importa? Não coloque no gprof expectativas que nunca foram reivindicadas por isso. É apenas uma ferramenta de medição e apenas de operações ligadas à CPU.

Tente isso em vez disso.
Aqui está um exemplo de uma aceleração de 44x.
Aqui está uma aceleração de 730x.
Aqui está uma demonstração em vídeo de 8 minutos.
Aqui está uma explicação das estatísticas.
Aqui está uma resposta para as críticas.

Há uma observação simples sobre os programas. Em uma determinada execução, toda instrução é responsável por uma fração do tempo total (especialmente callinstruções), no sentido de que, se não estivesse lá, o tempo não seria gasto. Durante esse tempo, a instrução está na pilha **. Quando isso é compreendido, você pode ver que -

O gprof incorpora certos mitos sobre desempenho, como:

  1. essa amostragem de contador de programa é útil.
    Só é útil se você tiver um gargalo de ponto de acesso desnecessário, como um tipo de bolha de uma grande variedade de valores escalares. Assim que você, por exemplo, alterá-lo para uma classificação usando comparação de cadeias, ainda é um gargalo, mas a amostragem do contador de programa não a verá porque agora o ponto de acesso está na comparação de cadeias. Por outro lado, se fosse amostrar o contador de programa estendido (a pilha de chamadas), o ponto no qual a comparação de cadeia é chamada, o loop de classificação, é claramente exibido. De fato, o gprof foi uma tentativa de remediar as limitações da amostragem somente para PC.

  2. que as funções de temporização são mais importantes do que capturar linhas de código demoradas.
    A razão para esse mito é que o gprof não foi capaz de capturar amostras de pilha; portanto, ele cronometra funções, conta suas invocações e tenta capturar o gráfico de chamada. No entanto, uma vez que uma função dispendiosa é identificada, você ainda precisa procurar dentro dela as linhas responsáveis ​​pelo tempo. Se houvesse amostras de pilha que você não precisaria procurar, essas linhas estariam nas amostras. (Uma função típica pode ter instruções de 100 a 1000. Uma chamada de função é 1 instrução; portanto, algo que localiza chamadas caras é de 2 a 3 ordens de magnitude mais precisas.)

  3. que o gráfico de chamadas é importante.
    O que você precisa saber sobre um programa não é onde ele gasta seu tempo, mas por que. Quando se gasta tempo em uma função, cada linha de código na pilha fornece um link na cadeia de raciocínio sobre o motivo de ela estar lá. Se você conseguir ver apenas parte da pilha, poderá ver apenas parte do motivo, portanto não poderá ter certeza se esse tempo é realmente necessário. O que o gráfico de chamadas diz a você? Cada arco informa que alguma função A estava no processo de chamar alguma função B por uma fração do tempo. Mesmo que A possua apenas uma linha de código chamada B, essa linha fornece apenas uma pequena parte do motivo. Se você tiver sorte o suficiente, talvez essa linha tenha um motivo insuficiente. Normalmente, você precisa ver várias linhas simultâneas para encontrar um motivo ruim, se houver. Se A ligar para B em mais de um local, será informado ainda menos.

  4. essa recursão é uma questão complicada e confusa.
    Isso ocorre apenas porque o gprof e outros criadores de perfil percebem a necessidade de gerar um gráfico de chamada e, em seguida, atribuem tempos aos nós. Se houver amostras da pilha, o custo de tempo de cada linha de código que aparece nas amostras é um número muito simples - a fração de amostras em que está. Se houver recursão, uma determinada linha poderá aparecer mais de uma vez em uma amostra. Não importa. Suponha que amostras sejam colhidas a cada N ms e a linha apareça em F% delas (isoladamente ou não). Se essa linha não puder demorar (por exemplo, excluindo-a ou ramificando-a), essas amostras desaparecerão e o tempo será reduzido em F%.

  5. que a precisão da medição do tempo (e, portanto, um grande número de amostras) é importante.
    Pense nisso por um segundo. Se uma linha de código estiver em três amostras em cinco, se você puder dispará-la como uma lâmpada, isso é aproximadamente 60% menos tempo que seria usado. Agora, você sabe que, se você tiver tirado 5 amostras diferentes, poderá vê-lo apenas 2 vezes ou até 4. Portanto, essa medição de 60% é mais como uma faixa geral de 40% a 80%. Se fosse apenas 40%, você diria que o problema não vale a pena consertar? Então, qual é a precisão do ponto no tempo, quando o que você realmente deseja é encontrar os problemas ? 500 ou 5000 amostras teriam medido o problema com maior precisão, mas não o teriam encontrado com mais precisão.

  6. que a contagem de chamadas de instruções ou funções é útil.
    Suponha que você saiba que uma função foi chamada 1000 vezes. Você pode dizer com isso que fração do tempo custa? Você também precisa saber quanto tempo leva para executar, em média, multiplicá-lo pela contagem e dividir pelo tempo total. O tempo médio de chamada pode variar de nanossegundos a segundos, portanto, a contagem por si só não conta muito. Se houver amostras de pilha, o custo de uma rotina ou de qualquer instrução é apenas a fração de amostras em que está. Essa fração de tempo é o que poderia, em princípio, ser salvo em geral, se a rotina ou a declaração pudesse ser feita com rapidez, e é essa a relação mais direta com o desempenho.

  7. que as amostras não precisam ser coletadas quando bloqueadas
    As razões para esse mito são duplas: 1) a amostragem por PC não faz sentido quando o programa está aguardando e 2) a preocupação com a precisão do tempo. No entanto, para (1) o programa pode muito bem estar aguardando algo que ele solicitou, como E / S de arquivo, que você precisa saber e quais exemplos de pilha revelam. (Obviamente, você deseja excluir amostras enquanto aguarda a entrada do usuário.) Para (2) se o programa estiver aguardando simplesmente por causa da concorrência com outros processos, isso presumivelmente acontece de maneira bastante aleatória enquanto estiver em execução. Portanto, embora o programa possa demorar mais, isso não terá um grande efeito sobre a estatística que importa, a porcentagem de tempo em que as instruções estão na pilha.

  8. que o "tempo próprio" é importante
    O tempo próprio só faz sentido se você estiver medindo no nível da função, não no nível da linha, e acha que precisa de ajuda para discernir se o tempo da função entra na computação puramente local versus nas rotinas chamadas. Ao resumir no nível da linha, uma linha representa o tempo próprio, se estiver no final da pilha, caso contrário, representa o tempo inclusivo. De qualquer forma, o que custa é a porcentagem de amostras de pilha em que está, de modo que a localiza para você em ambos os casos.

  9. que as amostras precisam ser colhidas em alta frequência
    Isso vem da ideia de que um problema de desempenho pode ter ação rápida e que as amostras precisam ser frequentes para atingi-lo. Mas, se o problema estiver custando 20%, digamos, de um tempo total de execução de 10 segundos (ou o que seja), cada amostra nesse tempo total terá 20% de chance de atingi-lo, não importa se o problema ocorrer em uma única peça como essa
    .....XXXXXXXX...........................
    .^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^(20 amostras, 4 ocorrências)
    ou em várias partes pequenas como esta
    X...X...X.X..X.........X.....X....X.....
    .^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^(20 amostras, 3 ocorrências)
    De qualquer forma, o número de ocorrências será em média de 1 em 5, não importa quantas amostras sejam coletadas, ou Quão poucos. (Média = 20 * 0,2 = 4. Desvio padrão = +/- sqrt (20 * 0,2 * 0,8) = 1,8.)

  10. que você está tentando encontrar o gargalo
    como se houvesse apenas um. Considere a seguinte linha do tempo de execução: vxvWvzvWvxvWvYvWvxvWv.vWvxvWvYvW
    Consiste em um trabalho útil real, representado por .. Existem problemas de desempenho que vWxYzlevam 1/2, 1/4, 1/8, 1/16, 1/32 do tempo, respectivamente. A amostragem encontra vfacilmente. Ele é removido, deixando
    xWzWxWYWxW.WxWYW
    Agora o programa leva metade do tempo para ser executado, e agora Wleva metade do tempo e é encontrado com facilidade. Ele é removido, deixando
    xzxYx.xY
    Esse processo continua, cada vez que você remove o maior problema de desempenho, em porcentagem, até que nada seja removido. Agora, a única coisa executada é a .que é executada em 1/32 do tempo usado pelo programa original. Este é o efeito de ampliação, pelo qual remover qualquer problema aumenta o restante, em porcentagem, porque o denominador é reduzido.
    Outro ponto crucial é que todos os problemas devem ser encontrados - sem nenhum dos 5. Qualquer problema não encontrado e corrigido reduz severamente a taxa de aceleração final. Apenas encontrar alguns, mas não todos, não é "bom o suficiente".

ADICIONADO: Gostaria apenas de salientar uma das razões pelas quais o gprof é popular - está sendo ensinado, provavelmente porque é gratuito, fácil de ensinar e já existe há muito tempo. Uma rápida pesquisa no Google localiza algumas instituições acadêmicas que ensinam (ou parecem):

berkeley bu clemson colorado duque earlham fsu indiana mit msu ncsa.illinois ncsu nyu ou princeton psu stanford ucsd umd umich utah utexas utk utk wustl

** Com exceção de outras maneiras de solicitar que o trabalho seja feito, isso não deixa rastro, indicando o porquê , como a postagem de mensagens.


3
@ Norman: Eu fiz um criador de perfil baseado nisso, em C para DOS, por volta de 93. Eu o chamei de mais um analisador de desempenho e o demonstrei nas reuniões do IEEE, mas isso foi o mais longe. Existe um produto da RotateRight chamado Zoom que não está muito longe. No * nix, o pstack é bom para fazer isso manualmente. Minha lista de tarefas pelo trabalho (produtos farmacêuticos no Windows) tem cerca de um quilômetro e meio de comprimento, o que impede projetos divertidos, sem mencionar a família. Isso pode ser útil: stackoverflow.com/questions/1777669/… #
Mike Dunlavey

6
Eu sempre achei os profilers não tão úteis para corrigir código lento e, em vez disso, usei bits seletivos de código de depuração para medir o tempo gasto por um grupo de instruções de minha escolha, muitas vezes auxiliado por algumas pequenas macros triviais ou qualquer outra coisa. Nunca me levou muito tempo para encontrar o culpado, mas sempre fiquei envergonhado com a minha abordagem de "peles de urso e facas de pedra" quando "todo mundo" (tanto quanto eu sei) usa as ferramentas sofisticadas. Obrigado por me mostrar por que nunca consegui obter as informações necessárias do criador de perfil. Essa é uma das idéias mais importantes que eu já vi no SO. Bem feito!
Wayne Conrad

7
@osgx: Não quero rasgar nada. É como um automóvel favorito antigo, simples e robusto, mas há coisas que ele não faz, e precisamos estar cientes disso, e não apenas isso, precisamos despertar dos mitos. Entendo que em algumas plataformas pode ser difícil obter amostras de pilha, mas se um problema é tal que o gprof não o encontra, o fato de ser a única ferramenta é um pequeno conforto.
Mike Dunlavey

2
@ Andrew: ... e se esse motivo se aplicar a uma fração significativa de amostras (como mais de 1), as linhas de código que poderiam eliminar essa atividade estão nessas amostras. Um gráfico pode dar uma dica disso, mas um número não grande de amostras de pilha simplesmente as mostrará.
Mike Dunlavey

2
@ Matt: Exemplos de problemas de desempenho de E / S encontrados desta maneira: 1) imprimir mensagens de log em um arquivo ou no console, que erroneamente foi considerado insignificante. 2) Convertendo entre texto e dobra em IO numérico. 3) E / S subterrâneas que extraem seqüências internacionalizadas durante a inicialização; as sequências que resultam não precisavam ser internacionalizadas. Eu bati muitos exemplos como estes.
precisa saber é o seguinte

63

Como não vi aqui nada sobre o perfque é uma ferramenta relativamente nova para criar um perfil dos aplicativos do kernel e do usuário no Linux, decidi adicionar essas informações.

Primeiro de tudo - este é um tutorial sobre criação de perfil do Linux comperf

Você pode usar perfse o seu Kernel Linux for maior que 2.6.32 ou oprofilese for mais antigo. Ambos os programas não exigem que você instrumente seu programa (como gprofexige). No entanto, para obter o gráfico de chamadas corretamente, perfvocê precisa criar seu programa -fno-omit-frame-pointer. Por exemplo: g++ -fno-omit-frame-pointer -O2 main.cpp.

Você pode ver a análise "ao vivo" do seu aplicativo com perf top:

sudo perf top -p `pidof a.out` -K

Ou você pode gravar dados de desempenho de um aplicativo em execução e analisá-los depois:

1) Para registrar dados de desempenho:

perf record -p `pidof a.out`

ou para gravar por 10 segundos:

perf record -p `pidof a.out` sleep 10

ou gravar com gráfico de chamadas ()

perf record -g -p `pidof a.out` 

2) Analisar os dados gravados

perf report --stdio
perf report --stdio --sort=dso -g none
perf report --stdio -g none
perf report --stdio -g

Ou você pode gravar dados de desempenho de um aplicativo e analisá-los depois disso, iniciando o aplicativo dessa maneira e esperando que ele saia:

perf record ./a.out

Este é um exemplo de criação de perfil de um programa de teste

O programa de teste está no arquivo main.cpp (colocarei main.cpp na parte inferior da mensagem):

Eu compilo desta maneira:

g++ -m64 -fno-omit-frame-pointer -g main.cpp -L.  -ltcmalloc_minimal -o my_test

Eu uso libmalloc_minimial.sodesde que ele é compilado -fno-omit-frame-pointerenquanto o libc malloc parece ser compilado sem essa opção. Então eu executo meu programa de teste

./my_test 100000000 

Depois, registro os dados de desempenho de um processo em execução:

perf record -g  -p `pidof my_test` -o ./my_test.perf.data sleep 30

Então analiso a carga por módulo:

relatório perf --stdio -g none --ort comm, dso -i ./my_test.perf.data

# Overhead  Command                 Shared Object
# ........  .......  ............................
#
    70.06%  my_test  my_test
    28.33%  my_test  libtcmalloc_minimal.so.0.1.0
     1.61%  my_test  [kernel.kallsyms]

A carga por função é analisada:

relatório perf --stdio -g none -i ./my_test.perf.data | c ++ filt

# Overhead  Command                 Shared Object                       Symbol
# ........  .......  ............................  ...........................
#
    29.30%  my_test  my_test                       [.] f2(long)
    29.14%  my_test  my_test                       [.] f1(long)
    15.17%  my_test  libtcmalloc_minimal.so.0.1.0  [.] operator new(unsigned long)
    13.16%  my_test  libtcmalloc_minimal.so.0.1.0  [.] operator delete(void*)
     9.44%  my_test  my_test                       [.] process_request(long)
     1.01%  my_test  my_test                       [.] operator delete(void*)@plt
     0.97%  my_test  my_test                       [.] operator new(unsigned long)@plt
     0.20%  my_test  my_test                       [.] main
     0.19%  my_test  [kernel.kallsyms]             [k] apic_timer_interrupt
     0.16%  my_test  [kernel.kallsyms]             [k] _spin_lock
     0.13%  my_test  [kernel.kallsyms]             [k] native_write_msr_safe

     and so on ...

Em seguida, as cadeias de chamadas são analisadas:

relatório perf --stdio -g graph -i ./my_test.perf.data | c ++ filt

# Overhead  Command                 Shared Object                       Symbol
# ........  .......  ............................  ...........................
#
    29.30%  my_test  my_test                       [.] f2(long)
            |
            --- f2(long)
               |
                --29.01%-- process_request(long)
                          main
                          __libc_start_main

    29.14%  my_test  my_test                       [.] f1(long)
            |
            --- f1(long)
               |
               |--15.05%-- process_request(long)
               |          main
               |          __libc_start_main
               |
                --13.79%-- f2(long)
                          process_request(long)
                          main
                          __libc_start_main

    15.17%  my_test  libtcmalloc_minimal.so.0.1.0  [.] operator new(unsigned long)
            |
            --- operator new(unsigned long)
               |
               |--11.44%-- f1(long)
               |          |
               |          |--5.75%-- process_request(long)
               |          |          main
               |          |          __libc_start_main
               |          |
               |           --5.69%-- f2(long)
               |                     process_request(long)
               |                     main
               |                     __libc_start_main
               |
                --3.01%-- process_request(long)
                          main
                          __libc_start_main

    13.16%  my_test  libtcmalloc_minimal.so.0.1.0  [.] operator delete(void*)
            |
            --- operator delete(void*)
               |
               |--9.13%-- f1(long)
               |          |
               |          |--4.63%-- f2(long)
               |          |          process_request(long)
               |          |          main
               |          |          __libc_start_main
               |          |
               |           --4.51%-- process_request(long)
               |                     main
               |                     __libc_start_main
               |
               |--3.05%-- process_request(long)
               |          main
               |          __libc_start_main
               |
                --0.80%-- f2(long)
                          process_request(long)
                          main
                          __libc_start_main

     9.44%  my_test  my_test                       [.] process_request(long)
            |
            --- process_request(long)
               |
                --9.39%-- main
                          __libc_start_main

     1.01%  my_test  my_test                       [.] operator delete(void*)@plt
            |
            --- operator delete(void*)@plt

     0.97%  my_test  my_test                       [.] operator new(unsigned long)@plt
            |
            --- operator new(unsigned long)@plt

     0.20%  my_test  my_test                       [.] main
     0.19%  my_test  [kernel.kallsyms]             [k] apic_timer_interrupt
     0.16%  my_test  [kernel.kallsyms]             [k] _spin_lock
     and so on ...

Então, neste ponto, você sabe onde seu programa gasta tempo.

E este é main.cpp para o teste:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

time_t f1(time_t time_value)
{
  for (int j =0; j < 10; ++j) {
    ++time_value;
    if (j%5 == 0) {
      double *p = new double;
      delete p;
    }
  }
  return time_value;
}

time_t f2(time_t time_value)
{
  for (int j =0; j < 40; ++j) {
    ++time_value;
  }
  time_value=f1(time_value);
  return time_value;
}

time_t process_request(time_t time_value)
{

  for (int j =0; j < 10; ++j) {
    int *p = new int;
    delete p;
    for (int m =0; m < 10; ++m) {
      ++time_value;
    }
  }
  for (int i =0; i < 10; ++i) {
    time_value=f1(time_value);
    time_value=f2(time_value);
  }
  return time_value;
}

int main(int argc, char* argv2[])
{
  int number_loops = argc > 1 ? atoi(argv2[1]) : 1;
  time_t time_value = time(0);
  printf("number loops %d\n", number_loops);
  printf("time_value: %d\n", time_value );

  for (int i =0; i < number_loops; ++i) {
    time_value = process_request(time_value);
  }
  printf("time_value: %ld\n", time_value );
  return 0;
}

Acabei de dar o seu exemplo e tirei 5 stackshots. Aqui está o que eles encontraram: 40% (aproximadamente) do tempo f1estava chamando delete. 40% (aproximadamente) do tempo process_requestestava ligando delete. Boa parte do restante foi gasta em new. As medições são difíceis, mas os pontos ativos são identificados.
precisa saber é o seguinte

O que é um stackshot? É isso que pstackproduz?

2
As in my answer, you run it under a debugger and hit ^C at a random time and capture the stack trace. 1) Acho que sua técnica não é útil quando você precisa analisar problemas de desempenho para um programa em execução no servidor do seu cliente. 2) Não tenho certeza de como você aplica essa técnica para obter informações de um programa com muitos threads que lidam com solicitações diferentes. Quero dizer, quando a imagem geral é bastante complicada.

2
Quanto ao número 1. Às vezes, os clientes ligam e dizem que seu programa funciona lentamente. Você não pode dizer imediatamente isso the problem is outside your code, pode? Como você pode precisar de algumas informações para apoiar seu argumento. Nessa situação, em algum momento você pode precisar criar um perfil do seu aplicativo. Você não pode simplesmente pedir ao seu cliente para iniciar o gdb, pressionar ^ C e obter pilhas de chamadas. Esse foi o meu ponto. Este é um exemplo spielwiese.fontein.de/2012/01/22/… . Eu tive esse problema e a criação de perfil ajudou muito.

2
Quanto ao # 2. Simplificar é uma boa abordagem, eu concordo. Às vezes funciona. Se um problema de desempenho ocorrer apenas no servidor de um cliente e você não puder reproduzi-lo no servidor, os perfis serão úteis.

21

Experimente o OProfile . É uma ferramenta muito melhor para criar um perfil do seu código. Eu também sugeriria o Intel VTune .

As duas ferramentas acima podem reduzir o tempo gasto em uma linha de código específica, anotar seu código, mostrar montagem e quanta instrução específica leva. Além da métrica de tempo, você também pode consultar contadores específicos, como hits de cache, etc.

Ao contrário do gprof, você pode criar um perfil de qualquer processo / binário em execução no seu sistema usando um dos dois.


2
Como também mencionado na resposta da valgrind, o Zoom do RotateRight ( rotateright.com ) fornece uma interface muito melhor e permite a criação de perfil remoto.
21410 JanePhanie

não gostava de oprofile, parecia casual
Matt Joiner

@ Matt algum ponto em particular?
Anycorn

Não foi capaz de lidar com mais de 10s de execução antes de gerar estouros de estatísticas, a saída não foi particularmente útil e a documentação é terrível.
Matt Joiner

1
@Tho OProfile: ARM, poder, ia64, ...
Anycorn



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.