C++ STL with jemalloc

11,178

Solution 1

C++ allows you to replace operator new. If this replacement operator new calls je_malloc, then std::allocator will indirectly call je_malloc, and in turn all standard containers will.

This is by far the simplest approach. Writing a custom allocator requires writing an entire class. Replacing malloc may not be sufficient (there's no guarantee that the non-replaced operator new calls malloc), and it has the risks noted earlier by Adrian McCarthy

Solution 2

If you want to replace malloc everywhere in your program (which I wanted to and also seems the only logical solution), then all you have to do is link against it.

So, if you use gcc then all you have to do is:

g++ yourprogram.cpp -ljemalloc

But, if it's not possible, then you have to use jemalloc via another functions e.g. je_malloc and je_free, and then you have to overload the new and delete operators.

There's no need for including any header if you don't use implementation-specific features (statistics, mostly).

Solution 3

Writing an allocator is going to be the easiest solution, since the stl was designed to have interchangeable allocators. This will be the easiest path.

Some projects play games try to get the alternate malloc implementation to replace the malloc and news provided by the compiler's companion library. That's prone to all sorts of issues because you end up relying on specific implementation details of your compiler and the library it normally uses. This path is fraught with danger.

Some dangers of trying to replace malloc globally:

  • Static initializer order has limited guarantees in C++. There's no way to guarantee the allocator replacement is initialized before the first caller tries to use it, unless you ban static objects that might allocate memory. The runtime doesn't have this problem, since the compiler and the runtime work together to make sure the runtime is fully initialized before initializing any statics.
  • If you dynamically link to the runtime library, then there's no way to ensure some of the runtime library's code isn't already bound to its own implementation. Trying to modify the compiler's runtime library might lead to licensing issues when redistributing your application.
  • All other methods of allocation might not always ultimately rely on malloc. For example, an implementation of new might bypass malloc for large allocations and directly call the OS to allocate memory. That requires tracking to make sure such allocations aren't accidentally sent to the replacement free.

I believe Chromium and Firefox has both replaced the allocator, but they play some dirty tricks and probably have to update their approach as the compiler, linker, and runtime evolve.

Solution 4

Make yourself allocator. Do like this:

#include <vector>

template<typename T>
struct RemoveConst
{
    typedef T value_type;
};

template<typename T>
struct RemoveConst<const T>
{
    typedef T value_type;
};

template <class T>
class YourAlloc {
public:
    // type definitions
    typedef RemoveConst<T>              Base;
    typedef typename Base::value_type   value_type;
    typedef value_type*                 pointer;
    typedef const value_type*           const_pointer;
    typedef value_type&                 reference;
    typedef const value_type&           const_reference;
    typedef std::size_t                 size_type;
    typedef std::ptrdiff_t              difference_type;

    // rebind allocator to type U
    template <class U>
    struct rebind {
        typedef YourAlloc<U> other;
    };

    // return address of values
    pointer address(reference value) const {
        return &value;
    }
    const_pointer address(const_reference value) const {
        return &value;
    }

    /* constructors and destructor
    * - nothing to do because the allocator has no state
    */
    YourAlloc() throw() {
    }
    YourAlloc(const YourAlloc&) throw() {
    }
    template <class U>
    YourAlloc(const YourAlloc<U>&) throw() {
    }
    ~YourAlloc() throw() {
    }

    // return maximum number of elements that can be allocated
    size_type max_size() const throw() {
        return std::numeric_limits<std::size_t>::max() / sizeof(T);
    }

    // allocate but don't initialize num elements of type T
    pointer allocate(size_type num, const void* = 0) {
        return (pointer)je_malloc(num * sizeof(T));
    }

    // initialize elements of allocated storage p with value value
    void construct(pointer p, const T& value) {
        // initialize memory with placement new
        new((void*)p)T(value);
    }

    // destroy elements of initialized storage p
    void destroy(pointer p) {
        // destroy objects by calling their destructor
        p->~T();
    }

    // deallocate storage p of deleted elements
    void deallocate(pointer p, size_type num) {
        je_free(p);
    }
};

// return that all specializations of this allocator are interchangeable
template <class T1, class T2>
bool operator== (const YourAlloc<T1>&,
    const YourAlloc<T2>&) throw() {
    return true;
}
template <class T1, class T2>
bool operator!= (const YourAlloc<T1>&,
    const YourAlloc<T2>&) throw() {
    return false;
}

int main()
{
    std::vector<int, YourAlloc<int>> vector;

    return 0;
}

The code is copied from here

Solution 5

There may be problems as the constructors won't be called. You may use differnt options of operator new (has more options than just new) which can just allocate memory without calling constructor, or call the constructor in already allocated memory. http://www.cplusplus.com/reference/std/new/operator%20new%5B%5D/

Share:
11,178
Sultan
Author by

Sultan

Updated on June 29, 2022

Comments

  • Sultan
    Sultan almost 2 years

    How is it possible to use C++ STL containers with jemalloc (or any other malloc implementation)?

    Is it as simple as include jemalloc/jemalloc.h? Or should I write an allocator for them?

    Edit: The application I'm working on allocates and frees relatively small objects over its lifetime. I want the replace the default allocator, because benchmarks showed that the application doesn't scale beyond 2 cores. Profiling showed that it was waiting for memory allocation, that's what caused the scaling issues. As I understand, jemalloc will help with that.


    I'd like to see a solution, that's platform-neutral as the application has to work on both Linux and Windows. (Linking against a different implementation is easy under Linux, but it's very hard on Windows as far as I know.)

  • Sultan
    Sultan about 12 years
    I updated my question to answer your's. What kind of issues are with replacing new?
  • Adrian McCarthy
    Adrian McCarthy about 12 years
    If you're just trying to replace new with the usual C++ shenanigans, you can get by. It's when people try to replace malloc throughout an entire program that gets really hairy.
  • Sultan
    Sultan about 12 years
    This is exactly what I want to do: replace malloc throughout the entire program. But I don't want to write my own implementation; I just want to use another (well tested) one.
  • Sultan
    Sultan about 7 years
    An allocator might be a good idea. If most of the data is stored in allocator-aware containers, then it is a very good solution.
  • MWB
    MWB over 4 years
    This might be outdated. G++ produces the exact same binaries with and without -ljemalloc in my experiments.
  • AnOccasionalCashew
    AnOccasionalCashew about 4 years
    @MaxB Try the -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free flags. Also double check your linking order and take a look at this manual page.