Aussie AI

Valgrind Limitation Workarounds

  • Book Excerpt from "Generative AI in C++"
  • by David Spuler, Ph.D.

Valgrind Limitation Workarounds

If you're a fan like me of Valgrind on Linux, especially the “Memcheck” tool, then you've probably noticed it has some major limitations. For starters, you might struggle to get Valgrind to cope with a huge engine and a full model, but even if that fails, it's still useful for finding memory bugs from running unit tests. Obviously, not running on Windows is also a biggie. Finally, there's also the problem that it cannot detect memory overruns on:

  • Global arrays or buffers
  • Static local arrays or buffers
  • Stack arrays or buffers

This is quite a major limitation! I'm not aware of any easy way to make Valgrind detect problems on these variables, but you can add code workarounds to increase the level of error detections. The trick is simply to re-compile your code to use allocated memory instead of global or stack arrays. Here's a simplistic idea:

    #if AUSSIE_COMPILE_FOR_VALGRIND
        char* buf = ::new char[BUFSIZE];
        buf[0] = 0;
        float *farr = ::new float[BUFSIZE];
        farr[0] = 0.0f;
    #else
        char buf[BUFSIZE] = "";
        float farr[BUFSIZE] = { 0 };
    #endif

There are several practical problems with this workaround method including:

    1. It requires a re-compile to switch between Valgrind and non-Valgrind modes.

    2. “sizeof buf” will be wrong if you're using it (e.g. for memset).

    3. Matching “delete” statements are needed, otherwise Valgrind finds leaked memory.

    4. Tons of boilerplate code that is prone to copy-paste bugs.

There is a method to fix the problem that this a full re-compile, not a runtime test. If you want to control this dynamically at runtime, you can do so at the cost of doubling your memory usage. Valgrind has a special builtin variable called “RUNNING_ON_VALGRIND” declared in “valgrind.h” that can be used.

    char hiddenbuf[BUFSIZE] = "";
    char* buf2 = hiddenbuf;
    if (RUNNING_ON_VALGRIND) {
        buf2 = ::new char[BUFSIZE];
        buf2[0] = 0; // Init!
    }

Note that on non-Linux platforms or production builds where you are not including “valgrind.h” then you'll get a compile error about RUNNING_ON_VALGRIND being an “undefined identifier”, and need to do something like this:

    #if !LINUX
    #define RUNNING_ON_VALGRIND 0
    #endif

Extension: Valgrind Smart Buffer Class: If you are feeling like a challenge, you can also define your own special “smart buffer” class, which hides these details behind constructor code that tests RUNNING_ON_VALGRIND. This class can also fix the problem that the allocated memory version doesn't free the memory properly by calling “delete” in the destructor. Since the destructor is automatically called whenever the smart buffer variable goes out of scope, you don't need to manually add any code at the end of a function using a smart buffer. This idea can work at run-time, fix the memory leaks, and avoid boilerplate, but still has the sizeof problem.

 

Next:

Up: Table of Contents

Buy: Generative AI in C++: Coding Transformers and LLMs

Generative AI in C++ The new AI programming book by Aussie AI co-founders:
  • AI coding in C++
  • Transformer engine speedups
  • LLM models
  • Phone and desktop AI
  • Code examples
  • Research citations

Get your copy from Amazon: Generative AI in C++