多线程池化分配器

Posted

技术标签:

【中文标题】多线程池化分配器【英文标题】:Multi-threaded Pooled Allocators 【发布时间】:2011-01-07 00:51:20 【问题描述】:

我在多线程应用程序中为 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。这是我测试或用于此类问题的最佳插件库。不,我不隶属于他们。 【参考方案1】:

为什么不直接构造一个引用 make_quadtree 的 local_alloc 实例?

【讨论】:

我正在考虑这个问题,但我不知道如何为 local_alloc(在 std::list 中调用)编写一个默认的 ctor 来分配引用/指针?? @Darren:不一定是默认构造的,你可以传入一个实例。 你的意思是我可以在构造LISTS的时候传递一个分配器的实例吗??因为我有一个列表向量而不仅仅是一个容器,所以这会起作用吗?当我调整向量大小时,新列表对象是否获得正确的分配器?对不起,我通常不这样做,我想我不明白...... @Darren:当然可以。它是一个构造函数参数,与任何其他构造函数参数的工作方式相同——当你需要一个新对象时,你可以传入它。 好的,我想我明白了,谢谢大家。我对调用 vector.resize() 的情况感到困惑。我担心它会导致调用默认 ctor(对于 std::list),但我现在意识到有第二个(默认)参数可以用来防止这种情况!【参考方案2】:

线程特定的分配器是相当大的挑战。

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

我在测试期间遇到了一些崩溃 商业许可是 昂贵的 它将系统调用“挂钩”到 malloc,我认为这种技术很狡猾。

所以我决定基于 boost::pool 和 boost::threadspecificptr 推出我自己的线程特定内存分配器。这需要少量,恕我直言,非常高级的 C++ 代码,但现在似乎运行良好。

我已经好几个月没有看这个细节了,但也许我可以再看一遍。

您正在寻找线程特定但不是线程安全分配器的评论。这是有道理的,因为如果分配器是特定于线程的,它就不需要是线程安全的。但是,根据我的经验,只要不发生争用,线程安全的额外负担是微不足道的。

然而,所有这些理论都很有趣,但我认为我们应该转向实践。我相信我们需要一个小型的、仪表化的独立程序来演示您需要解决的问题。我在 std::multiset 分配中遇到了一个非常相似的问题,并编写了您可以在此处看到的程序:Parallel reads from STL containers

如果你能写出类似的东西来显示你的问题,那么我可以检查我的线程特定内存分配器是否可以在你的情况下发挥优势。

【讨论】:

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

以上是关于多线程池化分配器的主要内容,如果未能解决你的问题,请参考以下文章

多线程(线程池原理)

详述Java线程池实现原理

详述Java线程池实现原理

Java线程池源码阅读

池化技术——自定义线程池

Java多线程与线程池技术