Answers:
正如我在这里提到的,我已经看到英特尔TBB的自定义STL分配器只需更改一个即可显着提高多线程应用程序的性能。
std::vector<T>
至
std::vector<T,tbb::scalable_allocator<T> >
(这是切换分配器以使用TBB的漂亮的线程专用堆的快速便捷的方法;请参阅本文档的第7页)
自定义分配器可能有用的一个领域是游戏开发,尤其是在游戏机上,因为它们只有少量的内存并且没有交换。在这样的系统上,您需要确保对每个子系统都具有严格的控制权,以使一个非关键系统无法从关键系统中窃取内存。诸如池分配器之类的其他东西可以帮助减少内存碎片。您可以在以下位置找到有关该主题的详细文章:
我正在使用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。随时将其用于您的项目。
我没有用自定义STL分配器编写C ++代码,但是我可以想象一个用C ++编写的Web服务器,它使用自定义分配器自动删除响应HTTP请求所需的临时数据。一旦生成响应,定制分配器就可以一次释放所有临时数据。
自定义分配器(我已经使用过)的另一个可能用例是编写一个单元测试,以证明函数的行为不依赖于其输入的某些部分。自定义分配器可以用任何模式填充内存区域。
与GPU或其他协处理器一起使用时,以特殊方式在主内存中分配数据结构有时是有益的。这种分配内存的特殊方式可以以方便的方式在自定义分配器中实现。
使用加速器时,通过加速器运行时进行自定义分配可能会有所帮助的原因如下:
我在这里使用自定义分配器;你甚至可能说这是要解决其他自定义动态内存管理问题。
背景:malloc,calloc,free和运算符new和delete的各种变体都有重载,而链接程序很高兴使STL为我们使用它们。这使我们能够执行以下操作:自动小对象池化,泄漏检测,分配填充,空闲填充,带有哨兵的填充分配,某些分配的缓存行对齐以及延迟释放。
问题是,我们正在嵌入式环境中运行-内存不足,无法在很长一段时间内正确地进行泄漏检测统计。至少不是在标准RAM中,而是通过自定义分配功能在其他地方提供了另一堆RAM。
解决方案:编写一个使用扩展堆的自定义分配器,并且仅在内存泄漏跟踪体系结构的内部使用它。这避免了跟踪器自身跟踪(并且还提供了一些额外的打包功能,我们知道跟踪器节点的大小)。
出于相同的原因,我们还使用它来保存功能成本分析数据。为每个函数调用和返回以及线程切换编写一个条目会很快变得昂贵。自定义分配器再次在较大的调试内存区域中为我们提供了较小的分配。
我正在使用自定义分配器来计算程序的一部分中分配/取消分配的数量并测量所需的时间。还有其他方法可以实现,但是这种方法对我来说非常方便。我只能对容器的一部分使用自定义分配器,这特别有用。
一种重要情况:编写必须跨模块(EXE / DLL)边界工作的代码时,必须仅在一个模块中进行分配和删除操作,这一点很重要。
我遇到的地方是Windows上的插件体系结构。至关重要的是,例如,如果跨DLL边界传递std :: string,则该字符串的任何重新分配都应从其起源的堆而不是DLL中可能不同的堆中进行*。
* 实际上,这比这要复杂得多,因为好像您正在动态链接到CRT一样,它仍然可以工作。但是,如果每个DLL都具有到CRT的静态链接,那么您将陷入痛苦的世界,幻像分配错误不断发生。
我曾经使用这些工具的一个例子是使用资源非常有限的嵌入式系统。假设您有2k的可用内存,而您的程序必须使用某些内存。您需要将4-5个序列存储在不在堆栈中的某个位置,此外,您还需要非常精确地访问这些内容的存储位置,在这种情况下,您可能需要编写自己的分配器。默认的实现可能会使内存碎片化,如果您没有足够的内存并且无法重新启动程序,那么这可能是不可接受的。
我正在研究的一个项目是在一些低功耗芯片上使用AVR-GCC。我们必须存储8个可变长度的序列,但是具有已知的最大值。内存管理的标准库实现是围绕malloc / free的瘦包装器,它通过在每个已分配的内存块前添加一个指向刚超过该已分配内存末尾的指针来跟踪放置项目的位置。在分配新的内存块时,标准分配器必须遍历每个内存块,以找到下一个可用块,该块将适合请求的内存大小。在台式机平台上,这对于这几个项目而言非常快,但是您必须记住,这些微控制器中有些相对较慢且原始。另外,内存碎片问题是一个巨大的问题,这意味着我们真的别无选择,只能采取其他方法。
所以我们要做的是实现我们自己的内存池。每个内存块都足够大,可以容纳我们所需的最大序列。这样会提前分配固定大小的内存块,并标记当前正在使用的内存块。我们通过保留一个8位整数来做到这一点,其中每个位代表是否使用了某个块。我们在此处权衡了内存使用情况,以试图使整个过程更快,在我们的案例中,这是有道理的,因为我们正在将该微控制器芯片推向接近其最大处理能力的水平。
在嵌入式系统的上下文中,还有很多次我可以看到编写自己的自定义分配器,例如,如果序列的内存不在主内存中,例如在这些平台上经常会出现这种情况。
强制性链接到Andrei Alexandrescu的CppCon 2015分配器演讲:
https://www.youtube.com/watch?v=LIb3L4vKZ7U
令人高兴的是,仅仅设计它们就可以使您想到如何使用它们的想法:-)
对于共享内存,至关重要的是,不仅要将容器头,而且还将其中包含的数据存储在共享内存中。
Boost :: Interprocess的分配器就是一个很好的例子。但是,正如您在这里可以读到的那样,所有这些都不足以使所有STL容器共享内存兼容(由于不同进程中的映射偏移不同,指针可能会“中断”)。
不久前,我发现此解决方案对我非常有用:用于STL容器的快速C ++ 11分配器。它在VS2017(〜5x)和GCC(〜7x)上略微加快了STL容器的速度。它是基于内存池的专用分配器。仅由于您需要的机制,它才可以与STL容器一起使用。
我个人使用Loki :: Allocator / SmallObject来优化小对象的内存使用-如果您必须使用少量的小对象(1到256字节),它将显示出良好的效率和令人满意的性能。如果我们谈论分配适量的许多不同大小的小对象,它的效率可能比标准C ++新/删除分配的效率高约30倍。另外,有一个特定于VC的解决方案称为“ QuickHeap”,它带来了最佳的性能(分配和释放操作仅读取和写入正被分配/返回到堆的块的地址,最多可达99.(9)%情况)。 —取决于设置和初始化),但代价是明显的开销—每个扩展区需要两个指针,每个新存储块需要一个指针。它'
标准C ++新增/删除实现的问题在于,它通常只是C malloc / free分配的包装,并且对较大的内存块(例如1024+字节)有效。它在性能方面有时会产生明显的开销,有时还会有用于映射的额外内存。因此,在大多数情况下,自定义分配器的实现方式是最大化性能和/或最小化分配小的(≤1024字节)对象所需的额外内存量。
在图形仿真中,我已经看到了用于
std::allocator
没有直接支持的对齐约束。