Seu código C++ está lento? Hora de olhar sob o capô
\n\nVocê escreveu um código C++ que parece perfeito à primeira vista. A compilação ocorreu sem erros, o programa inicia... mas funciona de forma lenta. Familiar? A mão já quer reescrever tudo em assembly ou adicionar multithreading com urgência. Não se apresse. Provavelmente, o problema não está na arquitetura, mas em algumas funções "quentes" que consomem 90% do tempo da CPU.
\n\nNo mundo Linux, temos um arsenal poderoso de ferramentas para perfilamento de código C++. Isso não é mágica, é ciência. Não adivinhamos, medimos. Neste guia, mostrarei quatro abordagens práticas: desde uma rápida "visão geral" até micro-benchmarking. Você aprenderá como perfilar C++ no Linux como um verdadeiro profissional.
\n\nPor que palpites são ruins e o perfilamento é bom
\n\nAntes de mergulhar no código, lembre-se da regra de ouro da otimização: "Não otimize o que não foi medido". Sua intuição é um mau conselheiro. Frequentemente, passamos horas otimizando uma função que é chamada uma vez por minuto e ignoramos um loop banal de 10 milhões de iterações.
\n\nO perfilamento responde a três perguntas principais:
\n- \n
- Onde o programa gasta mais tempo? \n
- Por que isso acontece (algoritmo ineficiente, muitas operações de E/S, falhas de cache)? \n
- O que mudou após suas correções? \n
Hoje, vamos analisar quatro ferramentas que cobrirão 99% das suas necessidades. Vamos lá!
\n\nFerramenta nº1: gprof — o clássico do gênero
\n\ngprof é o bom e velho profiler GNU integrado. Ele funciona em duas etapas: coleta de estatísticas durante a execução do programa e sua análise. Vantagens: não precisa instalar nada extra, está presente em todas as distribuições. Desvantagens: fornece resultados aproximados e não funciona com programas multithread prontos para uso.
\n\nComo usar o gprof
\n\nPasso 1. Compile o programa com a flag especial -pg:
g++ -pg -O2 my_program.cpp -o my_program\n\nObserve: usamos -O2. Perfilar uma compilação de depuração sem otimizações é inútil; você obterá uma imagem distorcida.
Passo 2. Execute o programa. Ele será um pouco mais lento, pois o gprof coleta dados.
\n./my_program\n\nPasso 3. Na pasta atual, aparecerá o arquivo gmon.out. Analise-o:
gprof my_program gmon.out > analysis.txt\n\nAbra analysis.txt e veja algo como:
Flat profile:\nEach sample counts as 0.01 seconds.\n % cumulative self self total\n time seconds seconds calls us/call us/call name\n 60.00 0.06 0.06 10000 6.00 6.00 slow_function\n 20.00 0.08 0.02 5000 4.00 4.00 fast_function\n 10.00 0.09 0.01 main\n\nVoilà! Vemos que slow_function ocupou 60% do tempo. Este é nosso candidato para otimização.
Exemplo de código para perfilamento
\n\nVamos escrever um programa simples que será perfilado com todas as ferramentas:
\n\n#include <iostream>\n#include <cmath>\n\nvoid slow_function(int n) {\n volatile double sum = 0.0; // volatile para o compilador não otimizar para zero\n for (int i = 0; i < n; ++i) {\n for (int j = 0; j < 1000; ++j) {\n sum += std::sin(i) * std::cos(j);\n }\n }\n std::cout << sum << std::endl;\n}\n\nvoid fast_function(int n) {\n volatile int count = 0;\n for (int i = 0; i < n; ++i) {\n count += i % 2;\n }\n std::cout << count << std::endl;\n}\n\nint main() {\n slow_function(10000);\n fast_function(50000);\n return 0;\n}\n\nCompile com a flag -pg e execute