令人信服的自定义C++分配器示例?

Naa*_*aff 166 c++ memory-management std memory-alignment allocator

有什么理由放弃std::allocator支持自定义解决方案?您是否遇到过正确性,性能,可扩展性等绝对必要的情况?有什么非常聪明的例子吗?

自定义分配器一直是我不太需要的标准库的一个功能.我只是想知道SO上的任何人是否可以提供一些令人信服的例子来证明他们的存在.

tim*_*day 113

正如我在这里提到的,我已经看到英特尔TBB的自定义STL分配器只需更改单个版本即可显着提高多线程应用程序的性能

std::vector<T>
Run Code Online (Sandbox Code Playgroud)

std::vector<T,tbb::scalable_allocator<T> >
Run Code Online (Sandbox Code Playgroud)

(这是切换分配器以使用TBB的漂亮线程私有堆的快速方便的方法;请参阅本文档的第7页)

  • 原始链接现已解散,但CiteSeer有PDF:http://citeseerx.ist.psu.edu/viewdoc/summary?doi = 10.1.1.71.8289 (7认同)
  • 谢谢你的第二个链接.使用分配器来实现线程私有堆是很聪明的.我喜欢这是一个很好的例子,说明自定义分配器在非资源受限(嵌入或控制台)的场景中具有明显的优势. (2认同)

Gru*_*bel 79

自定义分配器可用的一个领域是游戏开发,特别是在游戏控制台上,因为它们只有少量内存而且没有交换.在这样的系统上,您需要确保对每个子系统进行严格控制,这样一个不加批判的系统就无法从关键系统中窃取内存.池分配器等其他功能可以帮助减少内存碎片.您可以在以下位置找到有关该主题的详细论文:

EASTL - 电子艺术标准模板库

  • EASTL链接的+1:"在游戏开发者中,[STL]最根本的弱点是std分配器设计,正是这个弱点是创建EASTL的最大因素." (12认同)

Joh*_*oma 61

我正在研究一个mmap-allocator,它允许向量使用内存映射文件中的内存.目标是使用直接在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() { }
        };
}
Run Code Online (Sandbox Code Playgroud)

要使用它,请按如下方式声明STL容器:

using namespace std;
using namespace mmap_allocator_namespace;

vector<int, mmap_allocator<int> > int_vec(1024, 0, mmap_allocator<int>());
Run Code Online (Sandbox Code Playgroud)

例如,它可以用于在分配内存时记录.必要的是重新绑定结构,否则向量容器使用超类allocate/deallocate方法.

更新:内存映射分配器现在可在https://github.com/johannesthoma/mmap_allocator上获得,并且是LGPL.随意将它用于您的项目.

  • 从std :: allocator派生而来,实际上并不是编写分配器的惯用方法.您应该查看allocator_traits,它允许您提供最少的功能,而traits类将提供其余的功能.请注意,STL总是通过allocator_traits使用你的分配器,而不是直接使用你的分配器,所以你不需要自己引用allocator_traits从std :: allocator派生的动机并不多(尽管这个代码可能是一个有用的起点,无论如何). (16认同)

Tho*_*Low 25

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


Mar*_*ote 18

使用自定义分配器来使用内存池而不是堆可能很有用.这是许多其他人中的一个例子.

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

  • 或者,共享该内存池时. (3认同)

pts*_*pts 7

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

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

  • 看起来第一个例子是析构函数的工作,而不是分配器. (4认同)
  • 如果您担心您的程序依赖于堆中内存的初始内容,那么在 valgrind 中快速(即一夜之间!)运行会让您知道一种或另一种方式。 (2认同)
  • @拟人化:析构函数和自定义分配器将一起工作,析构函数将首先运行,然后删除自定义分配器,这将不会调用free(...),但是会调用free(...)稍后,当请求完成时。这可以比默认分配器更快,并减少地址空间碎片。 (2认同)

小智 7

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

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);
}
Run Code Online (Sandbox Code Playgroud)

推荐使用 Hinnant 的分配器样板: https://howardhinnant.github.io/allocator_boilerplate.html


Jør*_*ogh 6

我正在使用自定义分配器来计算我程序的一部分中的分配/解除分配数量并测量需要多长时间。还有其他方法可以实现,但这种方法对我来说非常方便。我可以将自定义分配器仅用于我的容器的一个子集,这一点特别有用。


小智 6

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

我遇到的地方是 Windows 上的插件架构。例如,如果您跨 DLL 边界传递 std::string,则该字符串的任何重新分配都发生在它起源的堆中,而不是 DLL 中可能不同的堆中,这一点很重要*。

*实际上比这更复杂,就好像您正在动态链接到 CRT 一样,这可能无论如何都可以工作。但是,如果每个 DLL 都有一个指向 CRT 的静态链接,那么您将进入一个痛苦的世界,幻象分配错误不断发生。


Seb*_*ian 6

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

使用加速器时通过加速器运行时自定义分配的原因如下:

  1. 通过自定义分配,加速器运行时或驱动程序会收到内存块的通知
  2. 此外,操作系统可以确保分配的内存块是页面锁定的(有些称为此固定内存),也就是说,操作系统的虚拟内存子系统可能无法移动或移除内存中的页面或从内存中删除页面
  3. 如果1.和2.保持并请求页面锁定内存块和加速器之间的数据传输,运行时可以直接访问主内存中的数据,因为它知道它在哪里,并且可以确定操作系统没有移动/移除它
  4. 这节省了一个内存副本,该内存副本将以非页面锁定方式分配的内存:数据必须在主内存中复制到页面锁定的暂存区域,加速器可以初始化数据传输(通过DMA )

  • ...不要忘记页面对齐的内存块。如果您正在与驱动程序(即通过 DMA 使用 FPGA)交谈并且不希望为 DMA 分散列表计算页内偏移量的麻烦和开销,这将特别有用。 (2认同)

lea*_*der 5

我在这里使用自定义分配器; 你甚至可能会说这是为了解决其他自定义动态内存管理问题.

背景:我们有malloc,calloc,free以及operator new和delete的各种变体的重载,并且链接器很高兴让STL为我们使用这些.这让我们可以做一些事情,比如自动小对象池,泄漏检测,分配填充,免费填充,带有哨兵的填充分配,某些分配的缓存行对齐以及延迟免费.

问题是,我们正在嵌入式环境中运行 - 没有足够的内存来实际在长时间内正确地进行泄漏检测.至少,不是在标准RAM中 - 通过自定义分配功能,在其他地方可以使用另一堆RAM.

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

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


shu*_*e87 5

我使用这些的一个例子是处理资源非常有限的嵌入式系统。假设您有 2k 空闲内存,并且您的程序必须使用其中的一些内存。您需要将 4-5 个序列存储在不在堆栈上的某个位置,此外您还需要非常精确地访问这些内容的存储位置,在这种情况下您可能需要编写自己的分配器。默认实现可能会产生内存碎片,如果您没有足够的内存并且无法重新启动程序,这可能是不可接受的。

我正在进行的一个项目是在一些低功耗芯片上使用 AVR-GCC。我们必须存储 8 个长度可变但最大值已知的序列。内存管理的标准库实现是一个围绕 malloc/free 的薄包装器,它通过在每个分配的内存块前面添加一个指向该分配的内存块末尾的指针来跟踪放置项目的位置。当分配新的内存块时,标准分配器必须遍历每个内存块,以找到适合所请求的内存大小的下一个可用块。在桌面平台上,这对于这几个项目来说非常快,但您必须记住,相比之下,其中一些微控制器非常慢且原始。此外,内存碎片问题是一个大问题,这意味着我们别无选择,只能采取不同的方法。

所以我们所做的就是实现我们自己的内存池。每个内存块都足够大,可以容纳我们需要的最大序列。这会提前分配固定大小的内存块并标记当前正在使用哪些内存块。我们通过保留一个 8 位整数来做到这一点,其中每一位代表是否使用了某个块。我们在这里权衡了内存使用量,试图使整个过程更快,在我们的例子中,这是合理的,因为我们正在推动这个微控制器芯片接近其最大处理能力。

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


ein*_*ica 5

Andrei Alexandrescu 在 CppCon 2015 上关于分配器的演讲的强制性链接:

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

好消息是,只要设计它们,你就会想到如何使用它们的想法:-)