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

有什么理由让std::allocator支持自定义解决scheme吗? 你是否遇到过正确性,性能,可扩展性等绝对必要的情况? 任何真正聪明的例子?

自定义分配器一直是标准库的一个function,我没有太多需要。 我只是想知道这里有没有人可以提供一些令人信服的例子来certificate它们的存在。

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

 std::vector<T> 

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

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

自定义分配器可能有用的一个领域是游戏开发,特别是在游戏控制台上,因为它们只有less量内存和交换空间。 在这样的系统上,你要确保你严格控制每个子系统,这样一个不加批判的系统就不能从关键系统中窃取内存。 其他的东西像池分配器可以帮助减less内存碎片。 你可以在这个主题上find一个详细的长篇论文:

EASTL – 电子艺术标准模板库

我正在做一个mmap分配器,它允许向量使用内存映射文件中的内存。 目标是使用直接位于由mmap映射的虚拟内存中的存储的向量。 我们的问题是提高真正的大文件(> 10GB)到内存中的读取,没有拷贝开销,因此我需要这个自定义分配器。

到目前为止,我有一个自定义分配器(从std :: allocator派生)的骨架,我认为这是一个很好的起点,编写自己的分配器。 随意以任何你想要的方式使用这段代码:

 #include <memory> #include <stdio.h> namespace mmap_allocator_namespace { 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>()); 

它可以用于例如内存分配时logging。 重新绑定什么是必要的,否则向量容器使用超类分配/释放方法。

更新:内存映射分配器现在可在https://github.com/johannesthoma/mmap_allocator和LGPL。; 随意使用它为您的项目。

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

使用自定义分配器来使用内存池而不是堆是非常有用的。 这是许多其他人的一个例子。

对于大多数情况下,这当然是一个不成熟的优化。 但在某些情况下(embedded式设备,游戏等)可能非常有用。

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

自定义分配器(我已经使用过)的另一个可能的用例是编写一个unit testing来certificate函数的行为不依赖于它的某些input部分。 自定义分配器可以用任何模式填充内存区域。

我在这里使用自定义分配器; 你甚至可以说它是解决其他自定义的dynamic内存pipe理。

背景:我们有malloc,calloc,free以及operator new和delete的各种变体的重载,而且连接器很高兴让STL为我们使用这些。 这可以让我们做一些事情,如自动小对象池,泄漏检测,分配填充,自由填充,哨兵填充分配,某些分配的caching线alignment,以及免费延迟。

问题是,我们正在embedded式环境中运行 – 周围没有足够的内存,无法在长时间内正确地进行泄漏检测。 至less,不是在标准RAM中 – 通过自定义分配函数,还有其他地方可用的另一堆RAM。

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

同样的原因,我们也使用它来保留function成本分析数据。 为每个函数调用和返回一个入口,以及线程切换,可以得到快速的昂贵。 自定义分配器在较大的debugging内存区域中再次给予我们较小的分配。

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

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

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

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

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

我遇到这种情况是Windows上的一个插件体系结构。 例如,如果您通过DLL边界传递std :: string,则重要的是string的任何重新分配都会从其发出的堆发生,而不是DLL中可能不同的堆。

这实际上比这更复杂,就像你dynamic链接到CRT一样,这可能会起作用。 但是,如果每个DLL都有一个到CRT的静态链接,那么你正在走向一个痛苦的世界,幻影分配错误不断发生。

我使用过的一个例子是使用非常资源有限的embedded式系统。 假设你有2k的免费内存,你的程序必须使用一些内存。 您需要将4-5个序列存储在不在堆栈中的位置,另外您需要非常精确地访问这些东西的存储位置,这种情况下您可能需要编写自己的分配器。 默认实现可以分割内存,如果没有足够的内存并且无法重新启动程序,这可能是不可接受的。

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

所以我们所做的就是实现我们自己的内存池 。 每块内存足够大,以适应我们需要的最大序列。 提前分配了固定大小的内存块,并标记了哪些内存块正在使用中。 我们通过保持一个8位整数来实现这一点,如果使用了某个块,则每个位表示一个整数。 我们在这里交换了内存使用,试图使整个过程更快,在我们的情况下,我们正在推动这个微控制器芯片接近它的最大处理能力是合理的。

在embedded式系统的上下文中,还有很多其他的时候我可以看到你自己的自定义分配器,比如如果这些序列的内存不在主内存中,就像在这些平台上经常出现的那样。

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

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

对Andrei Alexandrescu的CppCon 2015分配器谈话的强制性链接:

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

好的是,只要devise它们,你就会想到你将如何使用它们:-)

我个人使用Loki :: Allocator / SmallObject来优化小对象的内存使用情况 – 如果您必须使用适量的非常小的对象(1到256字节),它将显示出高效率和令人满意的性能。 如果我们讨论分配适量的许多不同大小的小物体,它可以比标准的C ++新/删除分配高出约30倍。 此外,还有一个名为“QuickHeap”的特定于VC的解决scheme,它带来了最佳性能(分配和释放操作,分别读取和写入块分配/返回到堆的地址,分别达到99个。 – 取决于设置和初始化),但代价是显着的开销 – 每个扩展需要两个指针,每个新的内存块需要一个额外的指针。 如果您不需要大量的对象大小,它就是创build和删除大量对象(10 000 ++)的最快解决scheme(它为每个对象大小创build一个单独的池,从1到1023字节在当前的实现中,初始化成本可能会贬低整体性能提升,但可以在应用程序进入性能关键阶段之前继续分配/取消分配一些虚拟对象。

标准C ++新/删除实现的问题在于,它通常只是C malloc / free分配的包装,对于更大的内存块(如1024+字节)来说,它是很好的工具。 它在性能方面有明显的开销,有时候也会用于映射的额外内存。 因此,在大多数情况下,自定义分配器是以最大化性能和/或最小化分配小(≤1024字节)对象所需的额外内存量的方式实现的。

在graphics模拟中,我已经看到用于自定义分配器

  1. std::allocator没有直接支持的alignment约束。
  2. 通过使用单独的池来实现短期(仅此框架)和长期分配,最大限度地减less碎片。

之前我发现这个解决scheme对我非常有用: 用于STL容器的Fast C ++ 11分配器 。 它在VS2017(〜5x)以及GCC(〜7x)上略微提高了STL容器的速度。 它是一个基于内存池的专用分配器。 它只能用于STL容器,这要归功于你所要求的机制。