如何显式管理异构类型(和大小)的多个池分配器

How to explicitly manage multiple pool allocators for heterogeneous types (and sizes)?

本文关键字:分配器 管理 何显式 异构 类型      更新时间:2023-10-16

场景:我有一个G类,它由(通常)从N类派生的数千个类型的对象组成。所有这些对象都有一个定义明确的生命周期。首先,构造了一个对象G,然后添加了N个派生对象,然后用G进行了一些计算,这不会改变N个派生的对象,然后G超出了范围,与之一起,组成N个派生目标。N派生对象又包含指向添加到同一G对象的其他N派生对象的指针或指针的标准容器。G表示具有异构节点的图。

我的目标是:

  1. 将分配每个N派生对象的成本降至最低
  2. 最大化属于同一G对象的N个派生对象的参考位置
  3. 通过为所有N个派生对象解除分配单个块来最大限度地降低解除分配的成本
  4. 能够定义多个独立的G对象,具有独立的生命周期-可能在没有线程同步的情况下在并发线程中管理这些独立的G目标

对我来说,这似乎需要多个池分配器——它们像使用堆栈一样进行分配。。。并且仅当池被破坏时才释放池分配。

我研究了提升池分配器,但没有找到为不同大小的异构对象建立多个独立池的方法。

接下来,我定义了自己的自定义分配器,但很快就发现,虽然我可以将其作为模板参数传递给标准容器,如std::vector、std::set、std::list等,但我可以指定池分配器的类型。。。我之所以失败,是因为我不能简单地指定两个原本独立的容器应该共享同一个(非全局)分配器池。我认识到,一种解决方案是使用静态/全局,并将自己限制为仅在一个线程中构造G对象。我还考虑过使用线程本地存储将自定义分配器与相关池相关联。。。但认为那很丑陋。两种方法都不直接支持同一线程中两个独立G对象的交错构造。

我是否忽略了Boost中现有的问题解决方案?

有没有比使用静态/全局或线程本地存储来实现我的目标更好的习惯用法?

更新

我已经阅读了Stroustrup的faq和boost::容器文档。起初,Boost::container让我深受鼓舞,但我很失望没有看到如何将有状态分配器与这些容器一起使用的具体示例。我已经能够将我最初的问题简化为。。。给定一个结构:

struct DataUnit { map<int,string> m; list<int> l; }

我如何确保,对于DataUnit的每个实例,都有一个单独的池,从中分配m和l的内部组成部分?如果我传递一个自定义分配器来映射和列出,m和l将获得该容器的独立实例。我最初认为我可以使用get_allocator()来初始化带有aerena/pool的分配器。。。但是,遗憾的是,allocate()是在向量<…>的默认构造函数之前调用的已返回。。所以我不能那样做。

更奇怪的是,我发现,在涉足boost::容器一段时间后。。。vanilla std容器有一个get_allocator()(在MSVC 2010和2012以及g++4.6.3上),这表明这些上下文中的标准库对boost::container有类似的状态分配器支持。

不幸的是,我仍然没有可行的解决方案来解决我最初的问题(尽管我现在可以更雄辩地表达它。)

更新2

谢谢,pmr,你的最后一条评论是我会给你一个"正确答案"——如果你把它归类为答案的话。:)找到boost::container后,我的问题是,我希望它的文档对任何新功能都是明确的,比如在构造时传递分配器对象。。。并且我没有正确检查boost::容器源代码。Boost::容器,我现在确信,它为我的上述所有问题提供了一个非常优雅和直观的(如果文档记录不好的话)解决方案。再次感谢!

警告:完全未经测试的代码。我不知道它是哪个"习语",但下面1.5页的代码应该可以解决你的问题。

class GraphNodeAllocator
{
    struct CMemChunk
    {
        CMemChunk* pNext;
        BYTE* data()
        {
            return static_cast<BYTE*>( static_cast<void*>( this + 1 ) );
        }
    };
    CMemChunk* m_pChunk; // Most recently allocated a.k.a. current chunk
    BYTE* m_pFirstByte;  // First free data byte within the current chunk
    size_t m_freeBytes;  // Count of free bytes within the current chunk
    static const size_t cbChunkAlloc = 0x10000; // 65536 bytes per single allocation
    static const size_t cbChunkPayload = cbChunkAlloc - sizeof( CMemChunk );
    void* Allocate( size_t sz )
    {
        if( sz > cbChunkPayload )
            return NULL;
        if( m_freeBytes >= sz )
        {
            // Current chunk has the space
            m_freeBytes -= sz;
            void* res = m_pFirstByte;
            m_pFirstByte += sz;
            return res;
        }
        // Need a new chunk
        CMemChunk* pChunk = static_cast< CMemChunk* >( malloc( cbChunkAlloc ) );
        if( NULL == pChunk )
            return NULL;
        pChunk->pNext = m_pChunk;
        m_pChunk = pChunk;
        m_pFirstByte = m_pChunk->data();
        m_freeBytes = cbChunkPayload;
        return Allocate( sz );
    }
public:
    inline GraphNodeAllocator(): m_pChunk( NULL ), m_pFirstByte( NULL ), m_freeBytes( 0 ) { }
    inline ~GraphNodeAllocator()
    {
        while( NULL != m_pChunk )
        {
            CMemChunk* pNext;
            pNext = m_pChunk->pNext;
            free( m_pChunk );
            m_pChunk = pNext;
        }
    }
    template<typename E>
    inline E* newNode()
    {
        void* ptr = Allocate( sizeof( E ) );
        if( NULL == ptr ) return NULL;
        return ::new( ptr ) E();
    }
};

附言:这个想法借鉴了微软的CAtlPlex类,这也是为什么大多数微软的模板容器(列表、映射、哈希映射)通常比STL类快2倍的首要原因。自从我放弃了使用std::vector、std::set、std::list等,转而使用ATL的等价物后,我变得更快乐了。