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

时间:2013-02-18 20:51:59

标签: c++ memory-management boost c++11

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

我的目标是:

  1. 最大限度地降低分配每个N派生对象的成本。
  2. 最大化属于同一G对象的N派生对象的引用位置。
  3. 通过为所有N派生对象释放单个块来最小化解除分配的成本。
  4. 能够定义多个独立的G对象,具有独立的生命周期 - 可以在没有线程同步的情况下在并发线程中管理这些独立的G对象。
  5. 对我来说,这似乎要求多个池分配器 - 分配好像使用堆栈......并且只在池被销毁时释放池化分配。

    我查看了boost池分配器 - 但没有找到为不同大小的异构对象建立多个独立池的方法。

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

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

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

    更新

    我已经阅读了Stroustrup的常见问题 - 以及boost :: container文档。起初,我对Boost :: container感到非常鼓舞,但很遗憾没有看到如何在这些容器中使用有状态分配器的具体示例。我已经能够简化我原来的问题......给出一个结构:

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

    我如何确保对于DataUnit的每个实例,都有一个池,m和l的内部成分是从哪个池中分配的?如果我将自定义分配器传递给map和list,则m和l将获得此容器的独立实例。我最初认为我可以使用get_allocator()来使用我的aerena / pool初始化分配器...但遗憾的是,在vector&lt; ...&gt;的默认构造函数之前调用allocate()。已经回来..所以我做不到。

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

    不幸的是,我仍然没有解决原始问题的可行解决方案(尽管我现在可以更有说服力地表达它。)

    更新2

    谢谢,pmr,你的最后评论是我给出的“正确答案” - 如果你把它归为一个答案。 :)我的问题,找到了boost :: container,是我原本期望它的文档显式有关任何新功能 - 比如在构造时传递allocator对象......而且我没有检查过boost :: container源代码代码正常。我现在确信,Boost :: container为我上面提到的所有问题提供了一个非常优雅和直观(如果记录不完整)的解决方案。再次感谢!

1 个答案:

答案 0 :(得分:1)

警告:完全未经测试的代码。我不知道它是哪个“成语” - 但下面的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();
    }
};

P.S。这个想法借鉴了微软的CAtlPlex类,这是大多数微软的模板容器(列表,地图,哈希图)通常比STL版本快2倍的首要原因。自从我放弃使用std :: vector,std :: set,std :: list等支持ATL的等价物后,我变得更快乐了。