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:

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: