使用jemalloc的C++ STL

16
如何将C++ STL容器与jemalloc(或任何其他malloc实现)一起使用?
是简单地包含jemalloc/jemalloc.h吗?还是应该为它们编写一个分配器?
编辑:我正在开发的应用程序在其生命周期内分配和释放相对较小的对象。我想替换默认的分配器,因为基准测试表明该应用程序无法在2个核心以上进行扩展。分析显示,它正在等待内存分配,这就导致了扩展问题。据我所知,jemalloc将有助于解决这个问题。
我希望看到一个平台中立的解决方案,因为该应用程序必须在Linux和Windows上运行。(在Linux下链接不同的实现很容易,但据我所知,在Windows上很难。)
5个回答

13

C++允许您替换operator new。如果此替换operator new调用je_malloc,则std::allocator将间接调用je_malloc,依次所有的标准容器也将使用这个新的分配方法。

这是目前最简单的方法。编写自定义分配器需要编写一个完整的类。替换malloc可能不足够(不能保证未替换的operator new调用malloc),并且正如Adrian McCarthy之前指出的那样,有风险。


7

如果你想在程序中全面替换malloc(我也希望如此,这似乎是唯一合理的解决方案),那么你只需要链接它。

所以,如果你使用的是gcc,那么你只需要:

g++ yourprogram.cpp -ljemalloc

但是,如果不可能的话,您必须通过其他函数(例如je_mallocje_free)使用jemalloc,然后重载newdelete运算符。

如果您不使用实现特定功能(主要是统计数据),则无需包含任何头文件。


@MaxB 请尝试使用-fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free标志。同时,请仔细检查您的链接顺序,并查看此手册页面 - AnOccasionalCashew

5
编写分配器将是最简单的解决方案,因为STL被设计为具有可互换的分配器。这将是最简单的路径。
一些项目试图玩游戏,尝试获取替代的“malloc”实现来替换编译器附带库提供的“malloc”和“new”。由于您最终依赖于编译器及其通常使用的库的特定实现细节,因此这很容易出问题。这条路充满了危险。
尝试全局替换“malloc”会带来一些危险:
- C++中静态初始化程序的顺序保证有限。除非禁止可能分配内存的静态对象,否则无法保证在第一个调用程序尝试使用它之前初始化分配器替换。运行时不会出现此问题,因为编译器和运行时一起工作,以确保在初始化任何静态对象之前完全初始化运行时。 - 如果动态链接到运行时库,则无法确保某些运行时库的代码尚未绑定到其自己的实现。尝试修改编译器的运行时库可能会导致在重新分发应用程序时出现许可问题。 - 所有其他分配方法可能并不总是最终依赖于“malloc”。例如,“new”的实现可能会通过直接调用操作系统来分配内存而绕过“malloc”进行大型分配。这需要跟踪以确保不会意外地将此类分配发送到替换的“free”。
我相信Chromium和Firefox都已经替换了分配器,但它们采用了一些不规则的技巧,并且可能需要随着编译器、链接器和运行时的演变更新方法。

我更新了我的问题来回答你的问题。 替换“new”会有哪些问题? - KovBal
如果你只是想用通常的C++把new替换掉,那么你可以做到。但当人们试图在整个程序中替换malloc时,情况就会变得非常棘手。 - Adrian McCarthy
这正是我想做的:在整个程序中替换 malloc。但我不想编写自己的实现,我只想使用另一个(经过充分测试的)实现。 - KovBal

3

制作自己的分配器。操作步骤如下:

#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;
}

这段代码是从这里复制的。


分配器可能是个好主意。如果大部分数据存储在支持分配器的容器中,那么这是一个非常好的解决方案。 - KovBal

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接