Profiling com gprof e perf
1. Introdução ao Profiling em C
Profiling é o processo de análise dinâmica de um programa para identificar onde o tempo de execução é gasto. Em Linguagem C, essa prática é especialmente crítica devido ao controle manual de memória, acesso direto a hardware e operações de baixo nível que podem esconder gargalos sutis. Diferente de linguagens gerenciadas, em C cada instrução conta — um loop mal otimizado ou uma função de alocação frequente pode degradar drasticamente o desempenho.
Existem duas abordagens principais de profiling:
- Instrumentação (gprof): O compilador insere hooks (código extra) em cada chamada de função para contar execuções e medir tempo gasto. Gera relatórios precisos de contagem de chamadas, mas adiciona overhead significativo.
- Amostragem (perf): O sistema operacional interrompe o programa em intervalos regulares (ou em eventos de hardware) e registra o contador de programa atual. Menos intrusivo, ideal para análise estatística de hot spots.
Use gprof quando precisar de um mapa completo de chamadas de funções em programas single-thread. Use perf para micro-otimizações, análise de cache, branch prediction e quando o overhead do gprof distorcer os resultados.
2. gprof: Profiling por Instrumentação
Para usar gprof, o programa deve ser compilado com a flag -pg:
gcc -pg -o meu_programa main.c funcoes.c
O compilador insere instruções de prólogo em cada função que registram o endereço de retorno e contabilizam o tempo. Ao executar o programa normalmente:
./meu_programa
Um arquivo gmon.out é gerado no diretório de trabalho. Para gerar o relatório legível:
gprof meu_programa gmon.out > relatorio.txt
O relatório contém:
- Flat profile: Lista funções ordenadas por tempo próprio (self seconds) — tempo gasto dentro da função excluindo chamadas internas.
- Call graph: Mostra quem chamou quem, número de chamadas e tempo acumulado (incluindo funções filhas).
- Index: Mapeia funções para números no gráfico de chamadas.
Exemplo de saída do flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
45.2 2.34 2.34 100000 0.023 0.051 bubble_sort
30.1 3.89 1.55 50000 0.031 0.031 swap
15.3 4.68 0.79 100000 0.008 0.008 compare
9.4 5.17 0.49 1 490.00 517.00 main
3. Limitações e Boas Práticas com gprof
O gprof tem limitações importantes:
- Overhead: A instrumentação pode aumentar o tempo de execução em 30-200%. Para programas com muitas funções pequenas, os resultados podem ser distorcidos.
- Multi-thread: gprof não suporta threads POSIX. Apenas a thread principal é monitorada; threads filhas não geram dados.
- Otimizações do compilador: Flags como
-O2ou-O3podem inlinar funções, removendo os hooks do gprof. Compile com-O0 -pgpara dados precisos, ou use-fno-inlinecom otimizações. - Bibliotecas estáticas: Se uma biblioteca foi compilada sem
-pg, suas funções não serão instrumentadas. Bibliotecas dinâmicas (.so) não são suportadas — gprof só funciona com código estaticamente linkado.
Boas práticas:
- Use um conjunto de dados representativo, mas reduzido, para diminuir o tempo de execução com overhead.
- Compare perfis com e sem -pg para avaliar o impacto da instrumentação.
- Para bibliotecas, recompile-as com -pg ou use profiling amostral (perf).
4. perf: Profiling Amostral no Linux
O subsistema perf_events do Linux permite coletar amostras baseadas em eventos de hardware e software. Para usar perf, o programa deve ser compilado com informações de depuração (-g):
gcc -g -O2 -o meu_programa main.c
Coleta de amostras
perf record -F 100 -g ./meu_programa
Onde:
- -F 100: taxa de amostragem de 100 Hz (padrão é 4000 Hz; valores menores reduzem overhead).
- -g: habilita gravação do call graph (pilha de chamadas).
- Eventos padrão: cycles (ciclos de CPU). Para outros: perf record -e cache-misses,instructions -F 1000.
Análise dos resultados
perf report
Exibe uma interface interativa com as funções que mais consomem CPU. Use perf annotate para ver o código assembly e fonte anotado com contagem de amostras:
perf annotate -s nome_da_funcao
Exemplo de saída do perf report:
Samples: 1K of event 'cycles:u', Event count (approx.): 850000000
Overhead Command Shared Object Symbol
35.2% meu_programa meu_programa [.] bubble_sort
22.8% meu_programa meu_programa [.] swap
15.1% meu_programa libc-2.31.so [.] __memcpy_avx_unaligned
10.3% meu_programa meu_programa [.] compare
5. Técnicas Avançadas com perf
Eventos personalizados com perf stat
Para medir métricas específicas sem instrumentar o código:
perf stat -e cycles,instructions,cache-misses,branch-misses ./meu_programa
Saída típica:
Performance counter stats for './meu_programa':
850,000,000 cycles
1,200,000,000 instructions
50,000,000 cache-misses
10,000,000 branch-misses
0.523456789 seconds time elapsed
Geração de Flame Graphs
Flame graphs visualizam a pilha de chamadas ao longo do tempo. Primeiro, gere dados no formato adequado:
perf script -f > out.perf
Depois, use scripts do repositório FlameGraph (https://github.com/brendangregg/FlameGraph):
./stackcollapse-perf.pl out.perf > out.folded
./flamegraph.pl out.folded > graph.svg
Comparação de versões com perf diff
Para identificar regressões de desempenho entre duas versões:
perf record -o perf_old.data ./programa_antigo
perf record -o perf_new.data ./programa_novo
perf diff perf_old.data perf_new.data
Mostra diferenças percentuais no consumo de CPU por símbolo.
6. Casos Práticos em C: Otimizando um Programa Real
Considere um programa que ordena um array de 100.000 inteiros usando bubble sort. Identificamos gargalo com gprof:
// main.c
#include <stdio.h>
#include <stdlib.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void bubble_sort(int arr[], int n) {
for (int i = 0; i < n-1; i++)
for (int j = 0; j < n-i-1; j++)
if (arr[j] > arr[j+1])
swap(&arr[j], &arr[j+1]);
}
int main() {
int n = 100000;
int *arr = malloc(n * sizeof(int));
for (int i = 0; i < n; i++) arr[i] = rand() % 1000;
bubble_sort(arr, n);
free(arr);
return 0;
}
Passo a passo com gprof:
- Compilar com
-pg -g -O0:
gcc -pg -g -O0 -o bubble main.c
- Executar com dados reduzidos (n=10000 para não demorar horas):
./bubble
gprof bubble gmon.out > profile.txt
-
Analisar:
bubble_sortconsome 45% do tempo,swap30%. Identificamos queswapé chamada muitas vezes. -
Refatorar: substituir bubble sort por qsort da libc:
int cmp(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
qsort(arr, n, sizeof(int), cmp);
Passo a passo com perf:
- Compilar versão otimizada com
-g -O2:
gcc -g -O2 -o bubble_opt main.c
- Coletar amostras:
perf record -F 1000 -g ./bubble_opt
perf report
-
Identificar hot spots: se
qsortainda aparece como gargalo, useperf annotatepara ver onde o tempo é gasto dentro da função de comparação. -
Otimizar: inline a função de comparação ou usar um algoritmo mais adequado (radix sort para inteiros).
7. Integração e Ferramentas Complementares
Combinar gprof e perf oferece uma visão completa:
- gprof para entendimento macro: quem chama quem, contagem de chamadas.
- perf para micro-análise: onde exatamente o tempo é gasto, eventos de cache, branch prediction.
Ferramentas complementares:
- gprof2dot (https://github.com/jrfonseca/gprof2dot): Converte saída do gprof em gráficos DOT para visualização.
- KCachegrind (https://kcachegrind.github.io/): Visualizador de dados de profiling que aceita saída do Valgrind e perf.
- Valgrind --tool=callgrind (https://valgrind.org/docs/manual/cl-manual.html): Profiling por instrumentação detalhada, incluindo simulação de cache. Mais lento que perf, mas mais preciso para análise de cache.
Para flame graphs, use os scripts de Brendan Gregg (https://github.com/brendangregg/FlameGraph), que convertem dados do perf script em visualizações interativas.
Referências
- Documentação oficial do gprof (GNU) — Manual completo com todas as opções de linha de comando e interpretação de relatórios.
- perf Wiki (kernel.org) — Página oficial do subsistema perf_events com tutoriais e exemplos de uso.
- Brendan Gregg - perf Examples — Guia prático com dezenas de exemplos de comandos perf para análise de desempenho.
- gprof2dot: Convert gprof output to DOT — Ferramenta para gerar gráficos de chamadas a partir de arquivos gmon.out.
- Valgrind Callgrind Manual — Documentação da ferramenta de profiling por instrumentação do Valgrind, alternativa ao gprof.
- FlameGraph: Visualization of profiled call stacks — Repositório com scripts para gerar flame graphs a partir de dados do perf e outras ferramentas.
- KCachegrind: A visualization tool for profiling data — Visualizador gráfico para dados de profiling, compatível com Callgrind e perf.