Existem inúmeras maneiras de se analisar o desempenho de um programa em um sistema operacional GNU/Linux. Neste texto vou falar um pouco sobre uma ferramenta bastante conveniente para sistemas embarcados, que você provavelmente já possui instalada no seu ambiente, e pode começar a utilizar a partir de agora sem ter que alterar nenhuma linha de código do seu programa.
O GNU profiler (gprof), faz parte do conjunto de ferramentas GNU Binary Utilities (binutils), e tem como principal funcionalidade apontar quanto tempo está sendo gasto em cada parte do seu programa, assim como uma série de estatísticas respectivas a chamadas de função no código.
O compilador utilizado precisa ser compatível com o gprof, e para isso nada melhor do que utilizar o GNU Compiler Collection, o famoso GCC, em nossos testes. Os passos para utilização da ferramenta são muito simples, e serão citados abaixo:
1) Compile e link seu programa utilizando a opção (flag) “pg”. No nosso exemplo o script vai ficar assim:
$gcc -pg -o main.out main.c
2) Execute o binário do seu programa. Isso fará com que ele insira no arquivo gmon.out as informações que serão utilizadas no nosso próximo passo.
$ ./main.out
3) Utilize o gprof para analisar o arquivo gerado no passo anterior e nos reportar as informações desejadas:
$ gprof ./main.out
Caso você prefira direcionar a saída para uma arquivo de texto, também pode ser feito:
$ gprof ./main.out gmon.out > report.txt
OBS: Como nosso objetivo é utilizar o profiler para analisarmos aplicações embarcadas, pode ser de interesse de muitos saber que essa etapa pode ser executada em seu computador de desenvolvimento. Basta que para isso os arquivos main.out e gmon.out sejam copiados para o ambiente desejado.
O relatório gerado pelo terceiro passo tem vários formatos de saída, que podem ser configurados a partir de outras flags. Mas vou dar uma breve explicação de como interpretar a saída padrão do gprof, que é dividida em duas partes:
- Flat profile: Mostra quanto tempo foi gasto em cada função, quais funções foram chamadas e quantas vezes;
- Call graph: Mostra, para cada função, quais funções a chamaram, e quais funções ela chamou, e a percentagem de tempo gasta em cada uma dessas chamadas.
Agora vamos para a prática, e como eu disse anteriormente essa é uma boa ferramenta para se usar em um ambiente embarcado por usar poucos recursos computacionais e por já vir instalado na maioria dos sistemas. Então, nada melhor do que testarmos essa ferramenta em uma das mais famosas placas de desenvolvimento para Linux embarcado atualmente: Beaglebone Black.
A placa que vou utilizar é da revisão C e está com o Debian 7.5, a mesma versão que já veio gravada nela. Caso você queira saber como trocar a imagem que roda na sua Beaglebone Black leia meu artigo que trata o assunto.
Para nosso exemplo preparei um repositório no GitHub, contendo um programa em C muito simples, que faz uso de funções diferentes para que possamos analisá-las com o gprof. Também escrevi um pequeno script que vai conter os comandos citados nos três passos que descrevi acima, entre outros que explicarei mais adiante.
A aplicação que iremos compilar e rodar é um programa que simplesmente incrementa uma variável 100 milhões de vezes, e depois chama uma função que, por meio da chamada de outras funções, acaba por decrementar a variável 100 milhões de vezes, fazendo com que seu valor retorne a zero, como no começo.
Segue o código:
#include <stdio.h>
static unsigned int decrement_25mi( unsigned int i); // Decrement 25000000x
static unsigned int decrement_50mi( unsigned int i); // Decrement 50000000x
static unsigned int decrement_100mi( unsigned int i); // Decrement 100000000x
int main(void)
{
unsigned int j, i=0;
for( j=0; j<100000000; j++) // Increment 100000000x
i++;
i = decrement_100mi(i); // Decrement 100000000x
printf("Counter = %u\n", i); // Print value
return 0;
}
static unsigned int decrement_25mi( unsigned int i)
{
unsigned int j;
for( j=0; j<25000000; j++)
i--;
return i;
}
static unsigned int decrement_50mi( unsigned int i)
{
i = decrement_25mi(i);
i = decrement_25mi(i);
return i;
}
static unsigned int decrement_100mi( unsigned int i)
{
i = decrement_50mi(i);
i = decrement_25mi(i);
i = decrement_25mi(i);
return i;
}
Então, para testarmos o exemplo, basta clonar o repositório e rodar o script na sua Beaglebone Black:
$ git clone https://github.com/igorTavares/gprof_example.git $ cd gprof_example $ ./test standard
Resultados na Beaglebone Black
Flat profile:
Figura 1: Resultado do teste – Flat profile
Call graph:
Figura 2: Resultado do teste – Call graph
Representação visual do call graph
Para conseguirmos visualizar de forma gráfica o resultado do profiler podemos utilizar um script em Python chamado gprof2dot.py na nossa Beaglebone Black. Esse script transforma a nossa saída do profiler em um dot graph.
E para finalizar utilizamos o comando dot do pacote Graphviz para convertermos o dot graph em uma imagem. Logo a sequência de comandos ficará assim:
PRIMEIRA PARTE: PRECISA rodar na BeagleBone Black:
# Compila $ gcc -pg -o main.out main.c # Executa $ ./main.out
SEGUNDA PARTE: NÃO PRECISA rodar na BeagleBone Black, pode ser em seu computador de desenvolvimento, apenas copie os arquivos gmon.out e main.out gerados nos passos anteriores para sua máquina.
# Caso não tenha Python e seu sistema for Debian, Ubuntu ou Mint $ apt-get install python # Caso não tenha Graphviz e seu sistema for Debian, Ubuntu ou Mint $ apt-get install graphviz # Gera o arquivo de report $ gprof ./main.out gmon.out > report.txt # Converte o report em dot graph $ ./gprof2dot.py report.txt > report.dot # Converte o dot graph em uma imagem $ dot -Tpng -o profile.png report.dot
Ou caso você tenha clonado o repositório na BeagleBone Black, basta executar:
$ ./test full
Para visualizar a imagem profile.png sem precisar ligar um monitor na BeagleBone Black, basta transferi-la para o seu computador de desenvolvimento e abri-la. Ou melhor ainda, rode a segunda parte dos comandos em seu computador de desenvolvimento e abra a imagem.
A imagem obtida do nosso exemplo foi:
Figura 3: Imagem profile.png, representação visual do call grapgh
A primeira impressão que esse fluxograma passa é de que tem muita informação “jogada” e que vai ser difícil digerir tudo isso. Mas é tranquilo de entender, e vou explicar o que significa tudo isso.
- Cada retângulo representa uma função do seu programa, e tem o seu respectivo nome escrito na primeira linha de cada elemento;
- Na segunda linha temos a porcentagem do tempo que a função levou para ser executada por completa em relação ao tempo que o programa demorou pra ser executado por inteiro. A cor dos retângulos é definida justamente por esse valor, onde as funções que gastam o maior tempo possuem cores mais próximas do vermelho, e quanto menos tempo mais perto do azul escuro;
- Na terceira linha, entre parenteses, temos a percentagem do tempo gasto pela função propriamente dita (sem contar o tempo gasto nas chamadas de funções) em relação ao tempo que o programa demorou pra ser executado por inteiro;
- Na última linha temos o número de vezes que a determinada função foi chamada por qualquer outra função;
- Nas setas temos a quantidade de vezes que uma função chamou a outra, além da porcentagem do tempo total de execução do programa que foi gasto nessas chamadas.







Muito bom cara, parabéns!
Oi, muito bom o artigo! Parabéns.
Por favor atualiza o link do gprof2dot.py. Ainda está apontando pro finado googlecode.
Realmente muito bom artigo.
Já utilizei o profiler da TI no eclipse, essa ferramenta é muito útil.