valgrind
What it's really useful for
valgrind is a suite of simple, command-line tools (if you run valgrind without specifying which tool, you get the default memcheck tool) that enables you to identify a whole lot of things, of which the following are especially interesting to us in terms of performance:
- Memory leaks - memory leaks render memory unavailable for reuse. If you leak away all your memory, and start using swap space, performance turns to sludge.
- Cache misses - this being the future, there are levels of memory. Small amounts of fast memory, large amounts of slow memory, and sometimes layers inbetween. Having to fetch data from the slow memory is a "cache miss", and it takes a lot longer than fetching the data from the fast memory (the "cache"). Being smart with your data layout and access can have phenomenal results.
How Valgrind works
The user executes Valgrind, providing the name of the executable of interest as a parameter. Valgrind then executes that program under a "synthetic CPU"; all the code being run is instrumented. This means that Valgrind can run on any executable without the need for any change in how that executable is built (although I suggest you build it with debug symbols added - note that this is not necessarily the same a a "debug build"), which is very helpful. What's less helpful is that this leads to a significant loss of speed; having the program run at a few percent of normal speed isn't uncommon.
Finding memory leaks with Valgrind
Many programs cause a significant number of what we might call "false positives"; being mistaken identification of something that's not a leak, or a memory leak in code we can't change (libraries, for example), or sometimes memory leaks that we just don't care about (it's not uncommon for a program to allocate memory once and just not bother to deallocate it; when the process finishes, the memory will all be reclaimed anyway, so the programmer simply leaves it). As such, this is another case where simple examples prove the most instructive.
A simple memory leak example
It doesn't get simpler than this:
1 2 3 4 | int main() { int* p = new int; } |
Build:
g++ -g memleak01.cpp -o memleak01
Run under valgrind:
valgrind ./memleak01
The output looks something like this:
==17725== Memcheck, a memory error detector ==17725== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==17725== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info ==17725== Command: ./memleak01 ==17725== ==17725== ==17725== HEAP SUMMARY: ==17725== in use at exit: 72,708 bytes in 2 blocks ==17725== total heap usage: 2 allocs, 0 frees, 72,708 bytes allocated ==17725== ==17725== LEAK SUMMARY: ==17725== definitely lost: 4 bytes in 1 blocks ==17725== indirectly lost: 0 bytes in 0 blocks ==17725== possibly lost: 0 bytes in 0 blocks ==17725== still reachable: 72,704 bytes in 1 blocks ==17725== suppressed: 0 bytes in 0 blocks ==17725== Rerun with --leak-check=full to see details of leaked memory ==17725== ==17725== For counts of detected and suppressed errors, rerun with: -v ==17725== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
So what do we have here? The tool in use is Memcheck, the default tool that is used if you don't specify one. The column on the left, ==17725==, is the process number in this particular instance. There is the typical version information, a reminded of the command being monitored, and then the results.
If we skip straight to the interesting bit, LEAK SUMMARY, we can see that there is a definite loss of 4 bytes. What did we expect? In the source code, an int is created using new, and never tidied up with a call to delete. That would suggest that on this particular test system, an int is of size 4 bytes. Valgrind advises us to repeat with --leak-check=full set to see more details.
valgrind --leak-check=full ./memleak01
==17970== Memcheck, a memory error detector ==17970== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==17970== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info ==17970== Command: ./memleak01 ==17970== ==17970== ==17970== HEAP SUMMARY: ==17970== in use at exit: 72,708 bytes in 2 blocks ==17970== total heap usage: 2 allocs, 0 frees, 72,708 bytes allocated ==17970== ==17970== 4 bytes in 1 blocks are definitely lost in loss record 1 of 2 ==17970== at 0x4C2B0E0: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==17970== by 0x40061E: main (memleak01.cpp:3) ==17970== ==17970== LEAK SUMMARY: ==17970== definitely lost: 4 bytes in 1 blocks ==17970== indirectly lost: 0 bytes in 0 blocks ==17970== possibly lost: 0 bytes in 0 blocks ==17970== still reachable: 72,704 bytes in 1 blocks ==17970== suppressed: 0 bytes in 0 blocks ==17970== Reachable blocks (those to which a pointer was found) are not shown. ==17970== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==17970== ==17970== For counts of detected and suppressed errors, rerun with: -v ==17970== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
More information tells us that the leaked memory was allocated using operator new, and gives the call stack at the time; in this case, new was called at line 3 of the source file, as expected. Part of the experiemental art is validating the null hypothesis; if we alter the source code and delete the int, the leak should vanish.
1 2 3 4 5 | int main() { int* p = new int; delete p; } |
Build:
g++ -g memleak01.cpp -o memleak01
Run under valgrind:
valgrind ./memleak01
The output looks something like this:
==18558== Memcheck, a memory error detector ==18558== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==18558== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info ==18558== Command: ./memleak02 ==18558== ==18558== ==18558== HEAP SUMMARY: ==18558== in use at exit: 72,704 bytes in 1 blocks ==18558== total heap usage: 2 allocs, 1 frees, 72,708 bytes allocated ==18558== ==18558== LEAK SUMMARY: ==18558== definitely lost: 0 bytes in 0 blocks ==18558== indirectly lost: 0 bytes in 0 blocks ==18558== possibly lost: 0 bytes in 0 blocks ==18558== still reachable: 72,704 bytes in 1 blocks ==18558== suppressed: 0 bytes in 0 blocks ==18558== Reachable blocks (those to which a pointer was found) are not shown. ==18558== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==18558== ==18558== For counts of detected and suppressed errors, rerun with: -v ==18558== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
So there we have it. A simple memory leak, spotted and identified using valgrind. Because I'm a suspicious kind of person, I disassembled the binary executable to check that the memory allocation hadn't been optimised out due to not being used, and it hadn't.
TODO:
- More elaborate leak checks
- More on some valgrind options
- Suppression files to hide leaks we don't care about or can't do anything about
- Scripting it to run automatic tests