Que outros programas fazem a mesma coisa que o gprof?
Que outros programas fazem a mesma coisa que o gprof?
Respostas:
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
.
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 call
instruçõ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:
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.
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.)
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.
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%.
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.
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.
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.
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.
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.)
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 vWxYz
levam 1/2, 1/4, 1/8, 1/16, 1/32 do tempo, respectivamente. A amostragem encontra v
facilmente. Ele é removido, deixando
xWzWxWYWxW.WxWYW
Agora o programa leva metade do tempo para ser executado, e agora W
leva 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.
Como não vi aqui nada sobre o perf
que é 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 perf
se o seu Kernel Linux for maior que 2.6.32 ou oprofile
se for mais antigo. Ambos os programas não exigem que você instrumente seu programa (como gprof
exige). No entanto, para obter o gráfico de chamadas corretamente, perf
você 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.so
desde que ele é compilado -fno-omit-frame-pointer
enquanto 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;
}
f1
estava chamando delete
. 40% (aproximadamente) do tempo process_request
estava ligando delete
. Boa parte do restante foi gasta em new
. As medições são difíceis, mas os pontos ativos são identificados.
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.
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.
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.
As ferramentas de desempenho do Google incluem um criador de perfil simples de usar. A CPU e o criador de perfil de heap estão disponíveis.
Dê uma olhada no Sysprof .
Sua distribuição já pode ter.
http://lttng.org/ se você deseja um rastreador de alto desempenho