看完了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持有的所有内存,而这两个接口都不
微信
支付宝