多线程池分配器

3
我在使用多线程应用时,使用池化内存分配器来为std::list对象分配内存时遇到了一些问题。
我关注的代码部分是每个线程函数独立运行(即没有线程之间的通信或同步),因此我想为每个线程设置单独的内存池,其中每个池都不是线程安全的(因此速度更快)。
我尝试使用共享的线程安全单例内存池,但发现性能很差,这是预料中的。
这是我正在尝试做的事情类型的大大简化版本。伪代码方式包含了很多内容,如果有点混乱请见谅。
/* The thread functor - one instance of MAKE_QUADTREE created for each thread
 */
class make_quadtree
{
private:

/* A non-thread-safe memory pool for int linked list items, let's say that it's 
 * something along the lines of BOOST::OBJECT_POOL
 */
    pooled_allocator<int> item_pool;

/* The problem! - a local class that would be constructed within each std::list as the
 * allocator but really just delegates to ITEM_POOL
 */
    class local_alloc
    {
    public :
    //!! I understand that I can't access ITEM_POOL from within a nested class like
    //!! this, that's really my question - can I get something along these lines to
    //!! work??
        pointer allocate (size_t n) { return ( item_pool.allocate(n) ); }
};

public :
    make_quadtree (): item_pool()    // only construct 1 instance of ITEM_POOL per
                                     // MAKE_QUADTREE object
    {
    /* The kind of data structures - vectors of linked lists
     * The idea is that all of the linked lists should share a local pooled allocator
     */
        std::vector<std::list<int, local_alloc>> lists;

    /* The actual operations - too complicated to show, but in general:
     *
     * - The vector LISTS is grown as a quadtree is built, it's size is the number of
     *   quadtree "boxes"
     *
     * - Each element of LISTS (each linked list) represents the ID's of items
     *   contained within each quadtree box (say they're xy points), as the quadtree
     *   is grown a lot of ID pop/push-ing between lists occurs, hence the memory pool
     *   is important for performance
*/
    }
};

我的问题实际上是,我希望每个线程函数对象都有一个内存池实例,但在每个线程函数中,多个std::list对象之间共享该内存池。


我知道你已经选择了一个答案。但是,如果这是Windows系统,请看一下Microquill的SmartHeap或HeapAgent。这是我测试或使用过的最好的附加库,可以解决这种问题。不,我与他们没有关联。 - JimR
2个回答

0
为什么不直接使用对make_quadtree的引用来构造local_alloc的实例呢?

我曾考虑过这个问题,但我无法想出如何为local_alloc编写默认构造函数(在std::list中会调用),以便分配引用/指针?? - Darren Engwirda
@Darren:它不必是默认构造的,你可以传递一个实例。 - Puppy
你的意思是我可以在构造LISTS时传递一个allocator的实例吗?由于我有一个列表向量而不仅仅是一个容器,这样做是否有效?当我调整向量大小时,新的列表对象是否会获得正确的allocator?抱歉,我通常不是这样做的,我不认为我理解了... - Darren Engwirda
@Darren:当然可以。这是一个构造函数参数,与任何其他构造函数参数一样-在您想要新对象时传递它。 - Puppy
好的,我明白了,非常感谢。我对调用vector.resize()的情况感到困惑。我担心它会导致默认构造函数被调用(对于std::list),但是现在我意识到有第二个(默认)参数可以用来防止这种情况发生! - Darren Engwirda

0

线程专用分配器是一个相当大的挑战。

我花了一些时间寻找现成的线程专用分配器。我找到的最好的是hoard(hoard.org)。这提供了显著的性能改进,但是hoard有一些严重的缺点:

  • 在测试期间我遇到了一些崩溃问题
  • 商业许可证费用昂贵
  • 它“钩取”malloc系统调用,这是我认为不可靠的技术。

因此,我决定基于boost::pool和boost::threadspecificptr自己编写线程专用内存分配器。这需要一小部分非常高级的C++代码,但现在似乎运行良好。

现在已经过去了几个月,我没有再仔细研究这个问题,但也许我可以再次看看它。

您提到您正在寻找一个线程专用但不是线程安全的分配器。这很有道理,因为如果分配器是线程专用的,它就不需要是线程安全的。然而,在我的经验中,只要不发生争用,线程安全的额外负担是微不足道的。

然而,所有这些理论都很有趣,但我认为我们应该转向实际问题。我相信我们需要一个小型的、有仪器的独立程序来演示您需要解决的问题。我曾经遇到过一个非常类似的std::multiset分配问题,并编写了您可以在此处看到的程序:STL容器的并行读取

如果您能编写类似的程序,展示您的问题,那么我可以检查一下我的线程特定内存分配器是否可以在您的情况下得到优化应用。


我听说过hoard(和其他一些线程分配器),但据我所知(可能是错误的!),它们仍然在某个阶段使用某种线程保护。我真的想设置本地非线程安全池... BOOST :: THREADSPECIFICPOINTER是否使用TLS(在Windows上使用__declspec(thread)),如果是,我认为我不能使用它,因为我正在编译*.dll文件。 - Darren Engwirda
@Darren:nedmalloc 和 tcmalloc 也使用线程缓存的本地区域(使用显式 TLS,因此它是 DLL 安全的),但任何生产级别的线程缓存分配器都会偶尔进入线程锁,大多数情况下是为了请求本地区域的新内存页,这也允许在一个线程中分配的内存在另一个线程中传递和释放。 - Necrolis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接