pool_allocator源码笔记

1 篇文章 0 订阅
1 篇文章 0 订阅

介绍

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_variables

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数。
pool bug
问题主要出在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函数如下

GetChunkForAlloc

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没有实际用处。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值