Yes, we're now running our Black Friday Sale. All Access and Pro are 33% off until 2nd December, 2025:
How to Profile C++ Code Running on Linux
Last updated: March 31, 2025
1. Introduction
Developers often worry about performance when working on large-scale C++ software. So, to avoid pitfalls, it’s a common best practice to profile the code. Profiling helps developers analyze program performance, identify bottlenecks, and optimize resource usage. Of course, as an outstanding development platform, Linux supports various code profiling tools like gprof, perf, and Valgrind, which provide detailed insights into CPU and memory usage for an efficient and optimized C++ program.
In this tutorial, we’ll discuss the need for profiling and explore different tools for profiling C++ code running on Linux.
2. Why We Profile C++ Code Running on Linux
As mentioned earlier, profiling enables developers to identify how efficient programs are and where optimizations are required.
Let’s highlight some key benefits of profiling C++ code running on Linux:
- identify memory allocations that are never freed
- analyze CPU utilization and system performance
- detect slow functions and inefficient code
- improve execution speed and resource management
- reduce I/O or computation bottlenecks
- spot issues before they become critical
- balance workloads and improve parallel execution
- enhance cache efficiency to minimize latency
- identify unnecessary system calls to reduce overhead
Overall, profiling is a dynamic program analysis technique that examines code while it’s running to ensure efficiency and optimization. Moreover, it helps developers maintain the program’s stability and scalability.
3. Preparing C++ Code for Profiling
Before diving into the practical demonstration of code profiling in Linux, let’s create and save a C++ program named baeldung_profiling.cpp:
$ cat baeldung_profiling.cpp
#include <iostream>
#include <chrono>
#include <thread>
using namespace std;
// CPU-intensive task (factorial calculation)
unsigned long long factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// Intentionally leaking memory for profiling
void memoryLeak() {
new int[100]; // Not freeing memory on purpose
}
// Simulating an I/O-bound task (we're adding a delay)
void ioBoundTask() {
this_thread::sleep_for(chrono::seconds(1));
}
int main() {
cout << "Baeldung Profiling Test" << endl;
auto start = chrono::high_resolution_clock::now();
// Running CPU-intensive task
cout << "Factorial(25) = " << factorial(25) << endl;
// Simulating memory leak
memoryLeak();
// Simulating an I/O-bound task
ioBoundTask();
auto end = chrono::high_resolution_clock::now();
cout << "Total Execution Time: "
<< chrono::duration<double>(end - start).count() << " seconds" << endl;
return 0;
}
We’ve created the above C++ factorial program with an intentional memory leak, a CPU-intensive task, and a delay to test the performance of Linux profilers.
4. C++ Code Profiling Tools
While there are many options for checking and analyzing code on Linux, several tools stand out when it comes to profiling:
- gprof
- perf
- Valgrind
If we require function-level performance insights, we can use gprof. Additionally, if we want to check real-time system performance and CPU analysis, we can use perf. On the other hand, if we need to detect memory leaks and invalid access attempts, Valgrind with Memcheck is the best option. Lastly, we can use Valgrind with Massif to analyze heap memory usage and optimize allocations.
So, let’s explore the gprof, perf, and Valgrind profilers in Linux to analyze C++ code.
5. Basic Profiling Using gprof
The gprof (also known as the GNU Profiler) is a lightweight and popular profiler that helps developers track the execution time of functions within a C or C++ program.
5.1. Installation
First, let’s install gprof on the Linux system:
$ sudo apt install binutils
The binutils package contains the gprof tool.
5.2. Compile With Profiling
Afterward, we can compile the baeldung_profiling.cpp program with profiling enabled using the -pg flag:
$ g++ -g -pg -o baeldung_profiling baeldung_profiling.cpp
The above command compiles the program with debugging and profiling enabled and generates the baeldung_profiling executable file.
Specifically, -p targets the prof profiler as it produces suitable output. On the other hand, -g enables general debugging information in the system-native format.
5.3. Run With Profiling
Next, we run the compiled program to execute the factorial calculation and generate the profiling data file named gmon.out automatically:
$ ./baeldung_profiling
Baeldung Profiling Test
Factorial(25) = 7034535277573963776
Total Execution Time: 1.0021 seconds
Thus, we see the output of the program.
5.4. View and Analyze Profiling Data
Now, we use gprof to analyze the profiling data stored in gmon.out and save the output to the gprof_report.txt file:
$ gprof baeldung_profiling gmon.out > gprof_report.txt
Finally, we can display the content of the gprof_report.txt file:
$ cat gprof_report.txt
Flat profile:
Each sample counts as 0.01 seconds.
no time accumulated
% cumulative self self total
time seconds seconds calls Ts/call Ts/call name
0.00 0.00 0.00 7 0.00 0.00 std::chrono::duration<long, std::ratio<1l, 1l> >::count() const
0.00 0.00 0.00 4 0.00 0.00 std::chrono::duration<long, std::ratio<1l, 1000000000l> >::count() const
0.00 0.00 0.00 3 0.00 0.00 std::chrono::duration<long, std::ratio<1l, 1l>
…
Notably, we can view function call statistics and execution times, which can help developers improve program performance.
6. Advanced Profiling Using perf
perf is a lightweight profiler that provides detailed real-time CPU and performance analysis, including CPU cycles, cache misses, and branch mispredictions.
6.1. Installation
We can install perf on Debian-based Linux systems by ensuring several packages are deployed in the environment:
$ sudo apt install linux-tools-common linux-tools-generic linux-tools-$(uname -r)
Here, we see that all linux-tools-* packages are installed.
6.2. Compile With Profiling
After installing perf, we compile the C++ program:
$ g++ -g -o baeldung_profiling baeldung_profiling.cpp
Thus, we ensure that the code includes profiling helpers.
6.3. Run With Profiling
Next, we profile the baeldung_profiling executable using perf, which records various CPU and performance metrics:
$ sudo perf record ./baeldung_profiling
Baeldung Profiling Test
Factorial(25) = 7034535277573963776
Total Execution Time: 1.00351 seconds
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.015 MB perf.data (111 samples) ]
Notably, we can observe perf collected and stored 111 samples of performance data.
6.4. View and Analyze Profiling Data
Lastly, we can view these metrics:
$ perf report
The command should open a file containing the perf report output:
Samples: 111 of event 'task-clock:ppp', Event count (approx.): 27750000
Overhead Command Shared Object Symbol
33.33% baeldung_profil [kernel.kallsyms] [k] 0xffffffff8250a114
28.83% baeldung_profil ld-linux-x86-64.so.2 [.] intel_check_word.constprop.
…
Finally, we can close this report by pressing Ctrl + X.
7. Memory Profiling With Valgrind
Valgrind is a powerful instrumentation framework for memory debugging, memory leak detection, and profiling.
7.1. Installation
Before moving ahead, we install Valgrind on the Linux system.
After this, we can check the installed version:
$ valgrind --version
valgrind-3.24.0
At this point, we have a working Valgrind installation.
7.2. Compile With Profiling
Of course, before profiling the code, we compile the C++ program:
$ g++ -g -o baeldung_profiling baeldung_profiling.cpp
Similar to all other cases, the -g flag ensures profilers have information to work with.
7.3. Perform Profiling and Analysis
At this point, we can use any of the seven tools offered by Valgrind to analyze C++ code. For demonstration, we use Memcheck and Massif.
In particular, we leverage Valgrind Memcheck to detect memory leaks and errors in the baeldung_profiling program:
$ valgrind --leak-check=full --show-leak-kinds=all ./baeldung_profiling
Eventually, a file should open displaying the memory leak results:
==4510== by 0x40065F8: _dl_init (dl-init.c:84)
==4510== by 0x40202C9: ??? (in /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)
==4510==
==4510== 72,704 bytes in 1 blocks are still reachable in loss record 4 of 4
...
==4510== LEAK SUMMARY:
==4510== definitely lost: 400 bytes in 1 blocks
==4510== indirectly lost: 0 bytes in 0 blocks
...
==4510== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Profiling timer expired
Additionally, we can use the Valgrind Massif tool for heap profiling and analyzing memory usage:
$ valgrind --tool=massif ./baeldung_profiling
==4621== Massif, a heap profiler
…
==4621== Command: ./baeldung_profiling
==4621==
Baeldung Profiling Test
Factorial(25) = 7034535277573963776
Total Execution Time: 1.01294 seconds
==4621==
Thus, upon successful execution, the above command generates an output file named massif.out.<pid>, where <pid> is the process ID of the executed program.
Finally, we can view and analyze a human-readable summary of heap memory usage from the output files of Massif or another specific file using its PID. To do so, we check the readable results of all files by executing ms_print:
$ ms_print massif.out.*
Here, we can observe the results in a file:
| ->01.18% (1,024B) 0x4B1FF5F: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:744)
| ->01.18% (1,024B) 0x4B1E6D4: _IO_new_file_xsputn (fileops.c:1243)
...
--------------------------------------------------------------------------------
n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B)
--------------------------------------------------------------------------------
6 2,430,694 74,152 74,128 24 0
7 2,434,292 1,440 1,424 16 0
8 2,435,399 408 400 8 0
This detailed information can help developers optimize code and ensure better performance, avoiding crashes.
8. Conclusion
In this article, we covered three C++ code profiling tools on Linux. Profiling helps developers identify performance bottlenecks, memory leaks, and other inefficiencies in their code.
If we require function-level performance insights, we can use gprof. Additionally, perf is useful for real-time CPU and system performance analysis. Of course, we can rely on Valgrind with Memcheck and Massif to detect memory leaks and analyze heap usage.