抛弃std::allocator以支持自定义解决方案的一些真正好的理由是什么?您是否遇到过这样的情况:它对于正确性、性能、可伸缩性等来说是绝对必要的?有什么聪明的例子吗?

自定义分配器一直是标准库的一个特性,但我并不太需要它。我只是想知道是否有人能提供一些令人信服的例子来证明他们的存在。


使用自定义分配器来使用内存池而不是堆可能会很有用。这只是众多例子中的一个。

对于大多数情况,这肯定是一个不成熟的优化。但它在某些情况下(嵌入式设备、游戏等)非常有用。


我没有使用自定义STL分配器编写c++代码,但我可以想象一个用c++编写的web服务器,它使用自定义分配器自动删除响应HTTP请求所需的临时数据。自定义分配器可以在生成响应后立即释放所有临时数据。

自定义分配器(我已经使用过)的另一个可能的用例是编写一个单元测试来证明函数的行为不依赖于它的某些输入。自定义分配器可以用任何模式填充内存区域。


正如我在这里提到的,我已经看到英特尔TBB的自定义STL分配器仅通过更改单个STL分配器就显著提高了多线程应用程序的性能

std::vector<T>

to

std::vector<T,tbb::scalable_allocator<T> >

(这是一种快速方便的方式切换分配器使用TBB的漂亮的线程私有堆;见本文件第7页)


我正在使用一个MySQL存储引擎,它的代码使用c++。我们使用一个自定义分配器来使用MySQL内存系统,而不是与MySQL竞争内存。它允许我们确保我们使用的内存是用户配置MySQL使用的内存,而不是“额外的”。


自定义分配器可以发挥作用的一个领域是游戏开发,特别是在游戏机上,因为它们只有少量内存,没有交换空间。在这样的系统上,您要确保对每个子系统都有严格的控制,这样一个不重要的系统就不能从一个重要的系统窃取内存。池分配器等其他功能可以帮助减少内存碎片。你可以在这里找到一篇关于这个主题的详细的长篇论文:

EASTL—Electronic Arts标准模板库


这里我使用的是自定义分配器;您甚至可以说它是为了绕过其他自定义动态内存管理。

背景:我们有malloc, calloc, free的重载,以及操作符new和delete的各种变体,并且链接器很高兴地让STL为我们使用这些。这让我们可以做一些事情,如自动小对象池,泄漏检测,分配填充,自由填充,填充分配与哨兵,缓存线对齐某些分配,和延迟释放。

问题是,我们正在一个嵌入式环境中运行——没有足够的内存来在一段较长的时间内正确地进行泄漏检测。至少,不是在标准RAM中——通过自定义分配函数,在其他地方还有另一堆RAM可用。

解决方案:编写一个使用扩展堆的自定义分配器,并且只在内存泄漏跟踪体系结构的内部使用它……其他所有内容默认为执行泄漏跟踪的普通新建/删除重载。这避免了跟踪器跟踪本身(并且提供了一些额外的打包功能,我们知道跟踪器节点的大小)。

出于同样的原因,我们也使用它来保存功能成本分析数据;为每个函数调用和返回编写一个条目,以及线程切换,成本会很快增加。自定义分配器再次在较大的调试内存区域中为我们提供较小的分配。


我正在使用一个自定义分配器来计算程序的一部分中的分配/释放的数量,并测量它需要多长时间。还有其他方法可以达到这个目的,但这个方法对我来说非常方便。特别有用的是,我只能对容器的一个子集使用自定义分配器。


一种基本情况:当编写必须跨模块(EXE/DLL)边界工作的代码时,必须保持分配和删除只发生在一个模块中。

我在Windows上的插件架构中遇到了这种情况。例如,如果你跨DLL边界传递一个std::string,任何字符串的重新分配都发生在它起源的堆中,而不是在DLL中的堆中,这可能是不同的*。

*实际上比这更复杂,如果你动态链接到CRT,这可能会工作。但是,如果每个DLL都有一个到CRT的静态链接,那么您将陷入痛苦的世界,在那里幻影分配错误不断发生。


我正在研究一个mmap-分配器,它允许向量使用内存 内存映射文件。我们的目标是让向量使用这样的存储 直接在由mmap映射的虚拟内存中。我们的问题是 提高真正大的文件(>10GB)的读取到内存,而不复制 开销,因此我需要这个自定义分配器。

到目前为止,我已经有了一个自定义分配器的骨架 (它来源于std::allocator),我认为这是一个很好的开始 指向写自己的分配器。请随意使用这段代码 以任何你想要的方式:

#include <memory>
#include <stdio.h>

namespace mmap_allocator_namespace
{
        // See StackOverflow replies to this answer for important commentary about inheriting from std::allocator before replicating this code.
        template <typename T>
        class mmap_allocator: public std::allocator<T>
        {
public:
                typedef size_t size_type;
                typedef T* pointer;
                typedef const T* const_pointer;

                template<typename _Tp1>
                struct rebind
                {
                        typedef mmap_allocator<_Tp1> other;
                };

                pointer allocate(size_type n, const void *hint=0)
                {
                        fprintf(stderr, "Alloc %d bytes.\n", n*sizeof(T));
                        return std::allocator<T>::allocate(n, hint);
                }

                void deallocate(pointer p, size_type n)
                {
                        fprintf(stderr, "Dealloc %d bytes (%p).\n", n*sizeof(T), p);
                        return std::allocator<T>::deallocate(p, n);
                }

                mmap_allocator() throw(): std::allocator<T>() { fprintf(stderr, "Hello allocator!\n"); }
                mmap_allocator(const mmap_allocator &a) throw(): std::allocator<T>(a) { }
                template <class U>                    
                mmap_allocator(const mmap_allocator<U> &a) throw(): std::allocator<T>(a) { }
                ~mmap_allocator() throw() { }
        };
}

为了使用它,像下面这样声明一个STL容器:

using namespace std;
using namespace mmap_allocator_namespace;

vector<int, mmap_allocator<int> > int_vec(1024, 0, mmap_allocator<int>());

例如,每当分配内存时,就可以使用它记录日志。什么是必要的 是重新绑定结构,否则向量容器使用超类分配/释放 方法。

更新:内存映射分配器现在可以在https://github.com/johannesthoma/mmap_allocator上获得,并且是LGPL。您可以在项目中使用它。


当使用gpu或其他协处理器时,以特殊的方式在主存中分配数据结构有时是有益的。这种特殊的内存分配方式可以在自定义分配器中以一种方便的方式实现。

在使用加速器时,通过加速器运行时进行自定义分配是有益的,原因如下:

through custom allocation the accelerator runtime or driver is notified of the memory block in addition the operating system can make sure that the allocated block of memory is page-locked (some call this pinned memory), that is, the virtual memory subsystem of the operating system may not move or remove the page within or from memory if 1. and 2. hold and a data transfer between a page-locked memory block and an accelerator is requested, the runtime can directly access the data in main memory since it knows where it is and it can be sure the operating system did not move/remove it this saves one memory copy that would occur with memory that was allocated in a non-page-locked way: the data has to be copied in main memory to a page-locked staging area from with the accelerator can initialize the data transfer (through DMA)


I personally use Loki::Allocator / SmallObject to optimize memory usage for small objects — it show good efficiency and satisfying performance if you have to work with moderate amounts of really small objects (1 to 256 bytes). It can be up to ~30 times more efficient than standard C++ new/delete allocation if we talk about allocating moderate amounts of small objects of many different sizes. Also, there's a VC-specific solution called "QuickHeap", it brings best possible performance (allocate and deallocate operations just read and write the address of the block being allocated/returned to heap, respectively in up to 99.(9)% cases — depends on settings and initialization), but at a cost of a notable overhead — it needs two pointers per extent and one extra for each new memory block. It's a fastest possible solution for working with huge (10 000++) amounts of objects being created and deleted if you don't need a big variety of object sizes (it creates an individual pool for each object size, from 1 to 1023 bytes in current implementation, so initialization costs may belittle the overall performance boost, but one can go ahead and allocate/deallocate some dummy objects before the application enters it's performance-critical phase(s)).

标准的c++ new/delete实现的问题是,它通常只是C malloc/free分配的包装器,它适用于较大的内存块,比如1024+字节。它在性能方面有显著的开销,有时还会占用额外的内存用于映射。因此,在大多数情况下,自定义分配器的实现方式是最大化性能和/或最小化分配小对象(≤1024字节)所需的额外内存量。


对于共享内存来说,不仅容器头存储在共享内存中,而且容器头包含的数据也存储在共享内存中,这一点至关重要。

Boost::Interprocess的分配器就是一个很好的例子。然而,正如你在这里读到的,这个allone是不够的,要使所有STL容器共享内存兼容(由于不同进程中的映射偏移量不同,指针可能会“中断”)。


One example of I time I have used these was working with very resource constrained embedded systems. Lets say you have 2k of ram free and your program has to use some of that memory. You need to store say 4-5 sequences somewhere that's not on the stack and additionally you need to have very precise access over where these things get stored, this is a situation where you might want to write your own allocator. The default implementations can fragment the memory, this might be unacceptable if you don't have enough memory and cannot restart your program.

One project I was working on was using AVR-GCC on some low powered chips. We had to store 8 sequences of variable length but with a known maximum. The standard library implementation of the memory management is a thin wrapper around malloc/free which keeps track of where to place items with by prepending every allocated block of memory with a pointer to just past the end of that allocated piece of memory. When allocating a new piece of memory the standard allocator has to walk over each of the pieces of memory to find the next block that is available where the requested size of memory will fit. On a desktop platform this would be very fast for this few items but you have to keep in mind that some of these microcontrollers are very slow and primitive in comparison. Additionally the memory fragmentation issue was a massive problem that meant we really had no choice but to take a different approach.

So what we did was to implement our own memory pool. Each block of memory was big enough to fit the largest sequence we would need in it. This allocated fixed sized blocks of memory ahead of time and marked which blocks of memory were currently in use. We did this by keeping one 8 bit integer where each bit represented if a certain block was used. We traded off memory usage here for attempting to make the whole process faster, which in our case was justified as we were pushing this microcontroller chip close to it's maximum processing capacity.

在嵌入式系统上下文中,我还可以看到编写自己的自定义分配器的其他情况,例如,如果序列的内存不在主ram中,而在这些平台上可能经常出现这种情况。


强制性链接到Andrei Alexandrescu 2015年CppCon关于分配者的演讲:

https://www.youtube.com/watch?v=LIb3L4vKZ7U

好处是,只是设计它们让你想到如何使用它们:-)


在图形模拟中,我看到自定义分配器用于

std::allocator不直接支持的对齐约束。 通过为短期分配(只是这个框架)和长期分配使用单独的池来最小化碎片。


前一段时间我发现这个解决方案对我非常有用:STL容器的快速c++ 11分配器。它略微加快了VS2017上的STL容器(~5倍)以及GCC上的STL容器(~7倍)。它是一种基于内存池的特殊用途的分配器。它可以与STL容器一起使用,这多亏了您所要求的机制。


自定义分配器是在释放内存之前安全地擦除内存的合理方法。

template <class T>
class allocator
{
public:
    using value_type    = T;

    allocator() noexcept {}
    template <class U> allocator(allocator<U> const&) noexcept {}

    value_type*  // Use pointer if pointer is not a value_type*
    allocate(std::size_t n)
    {
        return static_cast<value_type*>(::operator new (n*sizeof(value_type)));
    }

    void
    deallocate(value_type* p, std::size_t) noexcept  // Use pointer if pointer is not a value_type*
    {
        OPENSSL_cleanse(p, n);
        ::operator delete(p);
    }
};
template <class T, class U>
bool
operator==(allocator<T> const&, allocator<U> const&) noexcept
{
    return true;
}
template <class T, class U>
bool
operator!=(allocator<T> const& x, allocator<U> const& y) noexcept
{
    return !(x == y);
}

推荐使用Hinnant的allocator样板: https://howardhinnant.github.io/allocator_boilerplate.html)