Perfilado de código C++: encontrando cuellos de botella en la práctica

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: por qué el perfilado no es un lujo, sino una necesidad

\n

Cada desarrollador de C++ se ha enfrentado a la situación en la que un programa funciona, pero lo hace lentamente. Las conjeturas intuitivas sobre qué sección de código "se ralentiza" a menudo resultan erróneas. Sin datos precisos, corre el riesgo de optimizar lo que ya funciona rápido, pasando por alto los verdaderos "cuellos de botella". El perfilado es la única forma científica de medir el rendimiento y encontrar los puntos débiles.

\n

En este artículo, analizaremos tres potentes herramientas para el perfilado de código C++: gprof (estadístico), Perf (hardware) y Valgrind/Callgrind (simulación). Aprenderá a aplicarlos en la práctica, interpretar los resultados y encontrar rápidamente las secciones problemáticas.

\n\n

1. Perfilado estadístico con gprof: inicio rápido

\n

gprof es un perfilador GNU clásico que funciona basándose en el muestreo estadístico. No requiere permisos especiales y es ideal para un primer análisis.

\n\n

Cómo preparar el código

\n

Para que funcione gprof, debe compilar el programa con las banderas -pg y -g:

\n
g++ -pg -g main.cpp -o my_program\n./my_program
\n

Después de ejecutar el programa, aparecerá un archivo gmon.out en la carpeta actual. Para obtener un informe legible, ejecute:

\n
gprof my_program gmon.out > report.txt
\n\n

Ejemplo: encontrando un algoritmo lento

\n

Consideremos un programa simple que ordena un array de dos maneras:

\n
#include <iostream>\n#include <vector>\n#include <algorithm>\n#include <chrono>\n\nvoid bubble_sort(std::vector<int>& arr) {\n    for (size_t i = 0; i < arr.size(); ++i)\n        for (size_t j = 0; j < arr.size() - i - 1; ++j)\n            if (arr[j] > arr[j+1])\n                std::swap(arr[j], arr[j+1]);\n}\n\nvoid quick_sort_wrapper(std::vector<int>& arr) {\n    std::sort(arr.begin(), arr.end());\n}\n\nint main() {\n    std::vector<int> data(50000);\n    std::generate(data.begin(), data.end(), std::rand);\n    \n    auto copy1 = data;\n    auto copy2 = data;\n    \n    bubble_sort(copy1);\n    quick_sort_wrapper(copy2);\n    \n    return 0;\n}
\n

Ejecute el perfilado. En el informe de gprof verá una tabla de llamadas. bubble_sort ocupará el 95-99% del tiempo. Este es un cuello de botella obvio: reemplazar la ordenación de burbuja por std::sort dará una mejora instantánea.

\n\n

Interpretación de resultados

\n

En el informe, busque la columna % time: muestra la proporción de tiempo empleado en la función. Las funciones con un alto porcentaje son sus candidatas para la optimización.

\n\n

2. Perfilado por hardware con Perf: precisión a nivel de ciclo

\n

Perf es una herramienta moderna para Linux que utiliza contadores de hardware de la CPU. Proporciona una sobrecarga mínima y permite medir no solo el tiempo, sino también fallos de caché, fallos de predicción de saltos y otras métricas.

\n\n

Instalación y uso básico

\n
sudo apt install linux-tools-common linux-tools-generic  # Ubuntu/Debian\nperf stat ./my_program   # Estadísticas generales\nperf record ./my_program # Grabación del perfil\nperf report              # Visualización del informe
\n\n

Ejemplo: encontrando fallos de caché

\n

Supongamos que tenemos una función que procesa una matriz por filas:

\n
const int N = 4096;\nint matrix[N][N];\n\nvoid process_rows() {\n    for (int i = 0; i < N; ++i)\n        for (int j = 0; j < N; ++j)\n            matrix[i][j] *= 2;\n}\n\nvoid process_cols() {\n    for (int j = 0; j < N; ++j)\n        for (int i = 0; i < N; ++i)\n            matrix[i][j] *= 2;\n}
\n

Ejecute perf stat para cada función. Verá que process_cols tiene un orden de magnitud más de cache-misses. La razón

Blogs

Book Recommendations