¿Su código C++ funciona lento? Es hora de mirar bajo el capó
\n\nHa escrito un código en C++ que, a primera vista, es ideal. La compilación fue exitosa, el programa se inicia... pero funciona de alguna manera lenta. ¿Le suena conocido? La mano se estira para reescribir todo en ensamblador o agregar urgentemente múltiples hilos. No se apresure. Lo más probable es que el problema no esté en la arquitectura, sino en un par de funciones "calientes" que consumen el 90% del tiempo del procesador.
\n\nEn el mundo de Linux tenemos un arsenal potentísimo de herramientas para el perfilado de código C++. Esto no es magia, es ciencia. No adivinamos, medimos. En esta guía le mostraré cuatro enfoques prácticos: desde una rápida "vista general" hasta micro-benchmarking. Aprenderá cómo perfilar C++ en Linux como un verdadero profesional.
\n\nPor qué las suposiciones son malas y el perfilado es bueno
\n\nAntes de sumergirnos en el código, recuerde la regla de oro de la optimización: "No optimices lo que no se ha medido". Su intuición es un mal consejero. A menudo pasamos horas optimizando una función que se llama una vez por minuto, e ignoramos un bucle trivial de 10 millones de iteraciones.
\n\nEl perfilado da respuesta a tres preguntas principales:
\n- \n
- Dónde gasta el programa la mayor parte del tiempo? \n
- Por qué sucede esto (algoritmo ineficiente, muchas operaciones de entrada/salida, fallos de caché)? \n
- Qué cambió después de sus correcciones? \n
Hoy analizaremos cuatro herramientas que cubrirán el 99% de sus tareas. ¡Allá vamos!
\n\nHerramienta n.º 1: gprof — un clásico del género
\n\ngprof es el viejo y querido perfilador GNU integrado. Funciona en dos etapas: recopilación de estadísticas durante la ejecución del programa y su análisis. Ventajas: no necesita instalar nada adicional, está en cada distribución. Desventajas: da resultados aproximados y no funciona con programas multihilo de fábrica.
\n\nCómo usar gprof
\n\nPaso 1. Compilamos el programa con la bandera especial -pg:
g++ -pg -O2 my_program.cpp -o my_program\n\nTenga en cuenta: usamos -O2. Perfilar una compilación de depuración sin optimizaciones no tiene sentido, obtendrá una imagen distorsionada.
Paso 2. Ejecutamos el programa. Se ejecutará un poco más lento, ya que gprof recopila datos.
\n./my_program\n\nPaso 3. En la carpeta actual aparecerá un archivo gmon.out. Lo analizamos:
gprof my_program gmon.out > analysis.txt\n\nAbrimos analysis.txt y vemos 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\n¡Voilà! Vemos que slow_function ocupó el 60% del tiempo. Este es nuestro candidato para la optimización.
Ejemplo de código para perfilado
\n\nEscribamos un programa simple que perfilaremos con todas las herramientas:
\n\n#include <iostream>\n#include <cmath>\n\nvoid slow_function(int n) {\n volatile double sum = 0.0; // volatile, para que el compilador no optimice a cero\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\nCompílelo con la bandera -pg y ejecute