Profiling C++ Code: Finding Bottlenecks in Practice

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why Profiling is Not a Luxury, but a Necessity

\n

Every 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.

\n

In 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.

\n\n

1. Statistical Profiling with gprof: A Quick Start

\n

gprof is a classic GNU profiler that works based on statistical sampling. It requires no special privileges and is ideal for initial analysis.

\n\n

How to Prepare Your Code

\n

To use gprof, compile your program with the -pg and -g flags:

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

After running the program, a file named gmon.out will appear in the current directory. To get a readable report, run:

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

Example: Finding a Slow Algorithm

\n

Consider 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}
\n

Run 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.

\n\n

Interpreting the Results

\n

In 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\n

2. Hardware Profiling with Perf: Cycle-Level Precision

\n

Perf 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.

\n\n

Installation and Basic Usage

\n
sudo 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\n

Example: Finding Cache Misses

\n

Suppose we have a function that processes a matrix row by row:

\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

Run perf stat for each function. You will see that process_cols has an order of magnitude more cache-misses. The reason

Blogs

Book Recommendations