[普通]boost之pool源码解析

作者(passion) 阅读(1244次) 评论(0) 分类( boost)

看完了c++设计新思维中的内存池实现,再粗略看下boost中pool的实现。可以看出两者有些相似之处。

查看罗剑锋的boost程序库完全开发指南会发现对内存池pool讲解甚是简单
没有标明任何注意事项,认为使用简单,随便使用,其实里面有很多使用不当,反而会效果更差
boost中有如下几个类型的pool,object_pool,singleton_pool

我们一个一个来进行解析
poolfwd.hpp中有如下几个申明,保证了pool使用时不用指定模板参数
template <typename UserAllocator = default_user_allocator_new_delete>
class pool;

template <typename T, typename UserAllocator = default_user_allocator_new_delete>
class object_pool;


template <typename Tag, unsigned RequestedSize,
typename UserAllocator = default_user_allocator_new_delete,
typename Mutex = details::pool::default_mutex,
unsigned NextSize = 32,
unsigned MaxSize = 0>
struct singleton_pool;

//////////////////////////////////////////////////////////////////////////
先看pool的实现
而在pool.hpp中,如果没看到前面的pool前置申明,会想不明白,为什么pool可以不指定模板参数的
template <typename UserAllocator>
class pool: protected simple_segregated_storage<
typename UserAllocator::size_type>

构造函数指定的几个参数
explicit pool(const size_type nrequested_size,
const size_type nnext_size = 32,
const size_type nmax_size = 0)
默认情况下我们会指定第一个参数,即每次分配的大小(malloc分配的大小)
其实另外两个参数也是有含义的,以下是三个参数的含义
requested_size:需要外部传入
next_size, 默认为32
start_size:默认就是next_size
max_size:默认为0

比如我们设置pool的requestsize是32byte,那么它默认会为我们分配
next_size * requestsize这么一块大的内存,分配完后,会把next_size左移1位,就是变为了64.
当next_size * 32这么一块大的内存都被用完了,它就认为32太小,应该为你一次分配64*requestsize的内存给你用,有点简单粗犷呀
代码摘要如下:
void * malloc BOOST_PREVENT_MACRO_SUBSTITUTION()
{
    // Look for a non-empty storage
    if (!store().empty())
        return (store().malloc)(); // 如果已经分配了,直接在分配的里面找,找不到,就需要调用malloc_need_resize了
    return malloc_need_resize();
}
第一次是没有分配的,所以我们先看malloc_need_resize的实现
void * pool<UserAllocator>::malloc_need_resize()
{
    // No memory in any of our storages; make a new storage,

  //partition_size是4和request的最小公倍数。如果我们requestsize不是4的倍数,比如是30,那就更加倒霉了,partition_size会等于60的。

// 为什么是60,而不用32呢,因为如果32,每次会分配32byte的内存出去,会多分配两个。如果内存池里直接用30又会影响内存对齐(4对齐)

// 所以取公倍数,然后60用于分配两次。
    const size_type partition_size = alloc_size();
    const size_type POD_size = next_size * partition_size +
    details::pool::ct_lcm<sizeof(size_type), sizeof(void *)>::value + sizeof(size_type);
    char * const ptr = (UserAllocator::malloc)(POD_size);

// 分配出POD_size的大小出来.。如果requestsize是32, partition_size的大小如果是32。最后分配的大小是32*32=1024.如果是30(4与30的公倍数是60),则会分配60*32=1920,当然最后会分配多几个byte用于存放一些标记位信息
    ....
    if(!max_size)
        next_size <<= 1; // 如果max_size没有指定值,那么next_size就会乘以2,这次分配的next_size个小的chunk用完了,下次分配就会扩大一倍。所以用pool分配大内存是件很恐怖的事情
    else if( next_size*partition_size/requested_size < max_size)
        next_size = min BOOST_PREVENT_MACRO_SUBSTITUTION(next_size << 1, max_size*requested_size/ partition_size); // 如果限制了最大的max_size则会取小的一个

    再后面的代码就是从这个分配到的内存里面分配requestsize的大小出去
    有一个变量需要注意一下,first这个值是表示下次可以分配的空间的起始地址,如果需要内存,则将first返回出去,并且将first指向下一个空余的内存块
    如果first为0表示没有内存可用了,就是store().empty()为true了,需要再次分配内存了
    ...
    return (store().malloc)();
}
再看下另外一个接口ordered_malloc,这个接口要慎用,效率低,特别是pool下的这个接口,看下实现就明白了
void * pool<UserAllocator>::ordered_malloc(const size_type n)
{
...
    void * ret = store().malloc_n(num_chunks, partition_size);
    if (ret != 0)
        return ret;

    next_size = max BOOST_PREVENT_MACRO_SUBSTITUTION(next_size, num_chunks); // 看下此时待分配的next_size于num_chunk谁大就用谁,保证能够分配num_chunks个连续内存出去。此时如果这个时候分配的一块大内存
                                                                             // num_chunks>next_size相当于分配了num_chunks给外部用了,这块大内存与new出来的没有任何区别,因为其它的用户是用不了的,它一分配
                                                                             // 就被ordered_malloc这个用户给占满了      
    const size_type POD_size = next_size * partition_size +
    details::pool::ct_lcm<sizeof(size_type), sizeof(void *)>::value + sizeof(size_type);
    char * const ptr = (UserAllocator::malloc)(POD_size); // 分配一块新的,绝对能够满足num_chunk
    if (ptr == 0)
    return 0;
    const details::PODptr<size_type> node(ptr, POD_size);
}

void * simple_segregated_storage<SizeType>::malloc_n(const size_type n,
const size_type partition_size)
{   
    void * start = &first;
    void * iter;
    do
    {
        if (nextof(start) == 0)
        return 0;
        iter = try_malloc_n(start, n, partition_size);
    } while (iter == 0);
    ...
    return ret;
}
会遍历这个list去try_malloc_n这块连续的内存区域
再看try_malloc_n的实现,其实就是一个一个区找,看是否是可用的内存
void * simple_segregated_storage<SizeType>::try_malloc_n(
void * & start, size_type n, const size_type partition_size)
{
    void * iter = nextof(start);
    while (--n != 0)
    {
        void * next = nextof(iter);
        if (next != static_cast<char *>(iter) + partition_size)
        {
            // next == 0 (end-of-list) or non-contiguous chunk found. 进入这个if说明next为0了,后面没有课用内存了,此时只有n块可用的内存,不满足外部需求
            start = iter;
            return 0;
        }
        iter = next;
    }
    return iter;
}
pool总结:尽量分配小内存,小于64byte为益,64*32=2M了,在里面分配的区块总大小小于32,如果超过了,pool会为你分配64*64=4M以此类推下去
分配的大小最好是4的整数倍

尽量不要使用pool的ordered_malloc,这个效率低,如果分配不到连续的,它就会扩展内存。

//////////////////////////////////////////////////////////////////////////
再看object_pool的实现就简单很多了
object_pool就是继承自pool,只是大小使用了sizeof(T)的,并且会调用构造函数
看下构造函数就明白了
explicit object_pool(const size_type next_size = 32, const size_type max_size = 0)
    :pool<UserAllocator>(sizeof(T), next_size, max_size) { }

它的malloc就是调用pool的,它的厉害之处在于
element_type * construct()
{
    typedef T element_type;
    element_type* const ret = (malloc)();
    if (ret == 0)
        return ret;
    try 
    { 
        new (ret) element_type();  // 在ret区域调用构造函数,只支持默认构造函数
    }
    catch (...) 
    { 
        (free)(ret); 
        throw; 
    }
    return ret;
}

object_pool的注意事项与pool也比较类似。

 
前几天和人讨论pool,说用pool分配到过大(比如32,64后),不用会自动降下来,然后抽空验证了下 事实是如何的呢? pool实际分配到内存的函数是在pool.hpp中调用的UserAllocator::malloc 而实际归还内存的函数式UserAllocator::free。
写了个demo试了下,外部调用object_pool::free函数,发现不会进入断点UserAllocator::free,而实际上归还也只是归还给内存池,看了UserAllocator::free调用的地方有两处 release_memory:释放pool中所有未分配内存。purge_memory:释放pool持有的所有内存,而这两个接口都不


« 上一篇:wifi共享上网(至尊版wifi)
« 下一篇:shared_ptr使用注意事项
在这里写下您精彩的评论
  • 微信

  • QQ

  • 支付宝

返回首页
返回首页 img
返回顶部~
返回顶部 img