您的位置:首页 > 编程语言 > C语言/C++

pool_allocator源码笔记

2016-07-08 12:57 489 查看

介绍

C++中的自定义Allocator主要用来解决大量小对象的分配和释放非常耗时的问题。频繁调用在堆上分配释放内存的new/delete函数十分耗时的主要原因是处于用户空间的客户代码需要通过new/delete调用处于内核空间的系统函数,而在内核空间和用户空间频繁切换的话十分耗费时间。本人在实现大量字符串数据(2G+)的处理时,通过perf命令发现该问题十分突出。

本文介绍与STL兼容的自定义Allocator pool allocators for stl源码中潜藏的问题, 该代码由微软NLP组研究员编写,质量高可读性强,非常适合用来学习编写custom allocator。

“pool allocators for stl”是微软NLP组 Anthony Aue发表在Drdobbs上的一篇为node-based container提供更高效的自定义Allocator的文章,其设计使用

PoolAllocator -> BunchOfChunks-> PoolChunk -> Block

每个PoolChunk默认包含255个Block,每个Block为一个unsigned int的大小,表示一个small object.

PoolAllocator最多使用BunchOfChunks分配64字节大小的small object, 超过该大小将直接使用系统malloc函数分配。

3层由大至小的设计, 原理基本上跟Modern C++ Design Chapter 4上的Small Objects一模一样, 主要使用罗马尼亚大神Andrei Alexandrescu提出来的 Policy-based Design 和 嵌套template 定义allocator的threading policy/storage policy/growth policy, 代码非常规范。由于该文章发表于2005年,有些部分并不兼容C++ 11, 阅读其源码以后,现将值得注意的问题记录如下

命名问题



PoolChunk.hpp文件中,PoolChunk成员中的ALLOCSIZE表示每个small object的大小,INDICES_PER_ALLOC表示每个Chunk中包含的Block数而非下标数, m_iNumFreeChunks表示Chunk中的free Block数而不是free chunk数,这里的命名确实有些误导,看了至少我二十几分钟才明白。

多线程导致的语义不正确

pool.hpp中的BunchOfChunks结构表示内存中的若干PoolChunk的集合,其成员中有以下3个迭代器成员

m_itrCurrChunkAlloc // 指向上次调用Allocate的Chunk
m_itrCurrChunkDealloc // 指向上次调用Deallocate的Chunk
m_itrCurrEmptyChunk // 指向Empty Chunk(最多1个), 该指针值只作为标志,不直接使用, 其实应该是个bool flag


这3个迭代器主要用于优化相应的Allocate/Deallocate操作,每次PoolChunk::Allocate/Deallocate被调用时, 先搜索相应的迭代器所指向的MRU chunk,如果该Chunk满足相应的分配/释放Block条件,则该Allocate/Deallocate调用为O(1)操作, 如果不满足条件,则开始遍历所有Chunk的操作, 该操作为O(C), C为Chunk数。



问题主要出在BunchOfChunks::deallocate函数在FindChunkDealloc和DealWithEmptyChunk之间有时间窗,因此,当发现该Chunk为空并调用DealWithEmptyChunk对其进行处理的同时,有可能(虽然是小概率)该Chunk已经不为空了,例如以下执行顺序

Thread A :

执行第83行代码以后被切出, 将p所指向的block插入freelist头部


Thread B:

在同一Chunk上执行allocate完毕, 此时 m_itrCurChunkAlloc, m_itrCurChunkDealloc 均指向当前Chunk, m_itrCurEmptyChunk已经指向m_AllChunks.end()


Thread A:

恢复执行第85行, 此时该Chunk已经不处于Empty状态了, 而DealWithEmptyChunk函数如下






由于指向Empty Chunk的m_itrCurEmptyChunk已经指向end(), 除了加锁以外仅有第191行被执行, m_itrCurrEmptyChunk被指向此时实际并非为空的当前Chunk。而这已经语义不正确了。虽然代码中的m_itrCurrEmptyChunk**恰好**没有被直接使用,但是这只是一个幸运的意外!

由于刚读过非常烧脑的C++ concurrency in action Chapter 5-8, 这个bug还是比较容易看出来的。

volatile关键字没有意义

C++中的volatile关键字不像Java那样对于多线程编程很关键,相反, 该关键字几乎没用, 主要是由于volatile并不能保证程序语句不会被编译器打乱顺序, 要实现不直接使用memory barrier的同步,应该使用C++ 11中的std::atomic

关于volative为啥没用, 具体可以参考stackoverflow上的总结

why-is-volatile-not-considered-useful-in-multithreaded-c-or-c-programming

when-to-use-volatile-with-multi-threading

因此,Threading.hppz中的typedef volatile T VolatileType没有实际用处。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  stl allocator c++ Drdobbs