Introduction: Why Profiling is Not a Luxury, but a Necessity
\nEvery C++ developer has encountered a situation where a program works, but does so slowly. Intuitive guesses about which part of the code is "slow" are often wrong. Without precise data, you risk optimizing what is already fast, while overlooking the real bottlenecks. Profiling is the only scientific way to measure performance and find bottlenecks.
\nIn this article, we will cover three powerful tools for profiling C++ code: gprof (statistical), Perf (hardware), and Valgrind/Callgrind (simulation). You will learn how to apply them in practice, interpret the results, and quickly find problematic areas.
1. Statistical Profiling with gprof: A Quick Start
\ngprof is a classic GNU profiler that works based on statistical sampling. It requires no special privileges and is ideal for initial analysis.
How to Prepare Your Code
\nTo use gprof, compile your program with the -pg and -g flags:
g++ -pg -g main.cpp -o my_program\n./my_program\nAfter running the program, a file named gmon.out will appear in the current directory. To get a readable report, run:
gprof my_program gmon.out > report.txt\n\nExample: Finding a Slow Algorithm
\nConsider a simple program that sorts an array in two ways:
\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}\nRun the profiling. In the gprof report, you will see a call table. bubble_sort will account for 95-99% of the time. This is an obvious bottleneck — replacing bubble sort with std::sort will yield an immediate improvement.
Interpreting the Results
\nIn the report, look for the % time column — it shows the proportion of time spent in a function. Functions with a high percentage are your candidates for optimization.
\n\n2. Hardware Profiling with Perf: Cycle-Level Precision
\nPerf is a modern Linux tool that uses hardware CPU counters. It has minimal overhead and allows you to measure not only time but also cache misses, branch mispredictions, and other metrics.
Installation and Basic Usage
\nsudo apt install linux-tools-common linux-tools-generic # Ubuntu/Debian\nperf stat ./my_program # Overall statistics\nperf record ./my_program # Record a profile\nperf report # View the report\n\nExample: Finding Cache Misses
\nSuppose we have a function that processes a matrix row by row:
\nconst 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}\nRun perf stat for each function. You will see that process_cols has an order of magnitude more cache-misses. The reason