注:
这些代码是去年就写好了,所以代码里注释的年份是13年,中间也修改过没有添加注释说明,这次和上次介绍的内存块都是申请固定长度的内存块,不能是任意长度,任意长度实现比较困难设计大量的算法,如果你有兴趣可以查查类似内存管理算法之类的关键字,我有个写日志的模块式用到了非固定长度的内存,我是这么用的,申请几个内存池(这个后面介绍自己实现的), MemPool<8> MemPool<16> MemPool<32> MemPool<64> MemPool<128> 然后根据字符串的长度来放入不同的内存池中,超多128的就分成两截,分成两截的也没那么复杂,以后有机会了也介绍下自己写的那个日志模块,因为那个模块有点缺陷我也不怎么用,不过效率绝对的高
今天就说下在C++ 内存块 快速访问内存使用(一)中提到的第二种内存块的实现,和第一种内存块接口完全一致,主要考虑用来做内存池时可以无缝兼容。
基本的原理就是申请一块内存,在内存的开头预留出一段来标示后面的内存是否分配出去了,开头这一段为了减少长度就使用了位的方式,用一个位来标记一块内存是否分配出去,后面可以分配出多少个node前面就预留出多少个位,由于内存最小的长度是char(8个位),所以最好元素的数量是8的整数倍,当然这个工作就在这个block内部做了,如果你的block想申请的个数为9,内部会自动对齐到16个,即8的整数倍,小于8用8代替
下面就是新鲜的代码,里面的注释不是很规范,希望能解释的很清楚,还是那句话,希望牛人看完后给圈圈点点 ,自己造的轮子希望您也能用的上,驰骋沙场,挥鞭天下,下面有简单的测试结果
还有一块差点忘记说了,就是这两种内存块我都附加了一些追踪信息,就是简单判断内存是否写超了,起不了关键作用,但有作用总比没作用要好的多,这段追踪代码可以通过宏来关闭,如果想做的细一些,可以考虑在内存块的每个node的末尾多多申请一些内存来追踪,就是通过一定得手段把NODE_SIZE变大一些,每个node多出的内存写入一些特定的标记为来检查。那个静态变量tracememflag并没有赋初始值,其实不用赋初始值,里面乱会更好,你认为呢,两种内存块都是非线程安全的,这一块我也打算在内存池里来做,要是独立使用内存块就要注意了
/*
============================================================================================
内存块2 非线程安全
原理:
在这块内存的开头使用位来标示后面的内存是否分配出去
调用Malloc需要调用Free销毁 调用New需要调用Delete来销毁
使用New或者Delete函数 存在一些类型构造和析构的回调 不要在析构的回调中调用无参的Delete
在无参的Delete防止析构回调再次使用内存块分配和释放 做了一些处理
使用TRACE_MEM_BLOCK可以开启内存块一些信息的追踪
add 2013.11.22 by yuwf
Copyright (c), ...
=============================================================================================
*/
#ifndef _PALANTIR_MEM_BLOCK2_H
#define _PALANTIR_MEM_BLOCK2_H
#include "PLTTrace.h"
namespace Palantir
{
#ifdef PALANTIR_TRACE
#ifndef TRACE_MEM_BLOCK
#define TRACE_MEM_BLOCK
#endif
#endif
// _NODE_COUNT_ 应该为8的整倍数 否则内部会自动扩充8的倍数
// 内部有个位数组记录每个node是否分配
template<unsigned int _NODE_SIZE_, unsigned int _NODE_COUNT_ = 8>
struct MemBlock2
{
enum
{
_NODE_NUM_ = _NODE_COUNT_ == 0 ? 8 : _NODE_COUNT_%8 == 0 ? _NODE_COUNT_ : _NODE_COUNT_+(8-_NODE_COUNT_%8), // 调节node的个数应是8的倍数 主要是为了使portion位恰好和和char一致
NODE_SIZE = _NODE_SIZE_ == 0 ? 1 : _NODE_SIZE_, // node的大小
NODE_COUNT = _NODE_NUM_, // node的个数
PORTION_SIZE = NODE_COUNT/8, // 索引的大小
BUFFER_SIZE = NODE_COUNT*NODE_SIZE, // buffer 的大小
TRACE_SIZE = NODE_SIZE*2,
};
MemBlock2();
~MemBlock2();
void* Malloc();
// p 必须是从这个block上malloc获取的 外部要做检查
// 返回值表示是否成功删除
bool Free( const void* p );
// 把分配的全部回收到内存池中
void Free();
// 基于类型的函数
template<class T>
T* New()
{
return ::new(Malloc()) T();
}
template<class T, class T1>
T* New( const T1& param1 )
{
return ::new(Malloc()) T( param1 );
}
template<class T, class T1, class T2, class T3>
T* New( const T1& param1, const T2& param2, const T3& param3 )
{
return ::new(Malloc()) T( param1, param2, param3 );
}
template<class T, class T1, class T2, class T3, class T4>
T* New( const T1& param1, const T2& param2, const T3& param3, const T4& param4 )
{
return ::new(Malloc()) T( param1, param2, param3, param4 );
}
template<class T, class T1, class T2, class T3, class T4, class T5>
T* New( const T1& param1, const T2& param2, const T3& param3, const T4& param4, const T5& param5 )
{
return ::new(Malloc()) T( param1, param2, param3, param4, param5 );
}
template<class T, class T1, class T2, class T3, class T4, class T5, class T6>
T* New( const T1& param1, const T2& param2, const T3& param3, const T4& param4, const T5& param5, const T6& param6 )
{
return ::new(Malloc()) T( param1, param2, param3, param4, param5, param6 );
}
template<class T, class T1, class T2, class T3, class T4, class T5, class T6, class T7>
T* New( const T1& param1, const T2& param2, const T3& param3, const T4& param4, const T5& param5, const T6& param6, const T7& param7 )
{
return ::new(Malloc()) T( param1, param2, param3, param4, param5, param6, param7 );
}
template<class T, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
T* New( const T1& param1, const T2& param2, const T3& param3, const T4& param4, const T5& param5, const T6& param6, const T7& param7, const T8& param8 )
{
return ::new(Malloc()) T( param1, param2, param3, param4, param5, param6, param7, param8 );
}
template<class T, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9>
T* New( const T1& param1, const T2& param2, const T3& param3, const T4& param4, const T5& param5, const T6& param6, const T7& param7, const T8& param8, const T9& param9 )
{
return ::new(Malloc()) T( param1, param2, param3, param4, param5, param6, param7, param8, param9 );
}
// p是T类型 会调用 p->~T()
// 返回值表示是否成功删除
template<class T>
inline bool Delete( const void* p );
// 把分配的全部回收到内存块中
template<class T>
inline void Delete();
// 判断指针是否是这个block上的
inline bool Is_From( const void* p ) const;
// 已使用的数量
inline unsigned int Used_Count() const { return NODE_COUNT - free_num; };
// 未使用的数量
inline unsigned int Free_Count() const { return free_num; };
// 是否分配完毕
inline bool Is_Full() const { return free_num == 0 ; }
// 是否未使用
inline bool Is_Not_Use() const { return free_num == NODE_COUNT; }
protected:
// 没必要支持拷贝
MemBlock2( const MemBlock2& ) {};
MemBlock2& operator = ( const MemBlock2& ) { return *this; };
unsigned int free_num; // 未分配的个数 用户快速判断是否还有空间
unsigned char portion[PORTION_SIZE]; // 用位来标记data中的node是否分配出去 0:未分配 1:分配
unsigned char buffer[BUFFER_SIZE];
#ifdef TRACE_MEM_BLOCK
unsigned char tracemem[TRACE_SIZE]; // 用来追踪这个块是否写超了
static unsigned char tracememflag[TRACE_SIZE];
#endif
};
#ifdef TRACE_MEM_BLOCK
template<unsigned int _NODE_SIZE_, unsigned int _NODE_COUNT_>
unsigned char MemBlock2<_NODE_SIZE_,_NODE_COUNT_>::tracememflag[MemBlock2<_NODE_SIZE_,_NODE_COUNT_>::TRACE_SIZE];
#endif
template<unsigned int _NODE_SIZE_, unsigned int _NODE_COUNT_>
MemBlock2<_NODE_SIZE_,_NODE_COUNT_>::MemBlock2() : free_num(NODE_COUNT)
{
memset( portion, 0, sizeof(portion) );
#ifdef TRACE_MEM_BLOCK
memcpy( tracemem, tracememflag, sizeof(tracemem) );
#endif
}
template<unsigned int _NODE_SIZE_, unsigned int _NODE_COUNT_>
MemBlock2<_NODE_SIZE_,_NODE_COUNT_>::~MemBlock2()
{
#ifdef TRACE_MEM_BLOCK
for ( int i = 0; i < PORTION_SIZE; ++i )
{
if ( portion[i] )
{
for ( int k = 7; k >= 0; --k )
{
if ( portion[i] & (1 << k) )
{
PLT_Trace( "%s 分配出去的内存没有收回 %p\n", __FUNCTION__, (&buffer[(i*8+(7-k))*NODE_SIZE] ) );
}
}
}
}
if ( memcmp( tracemem, tracememflag, sizeof(tracemem) ) != 0 )
{
PLT_Trace( "%s 内存块写超了\n", __FUNCTION__ );
}
#endif
}
template<unsigned int _NODE_SIZE_, unsigned int _NODE_COUNT_>
void* MemBlock2<_NODE_SIZE_,_NODE_COUNT_>::Malloc()
{
if ( Is_Full() )
{
return nullptr;
}
for ( int i = 0; i < PORTION_SIZE; ++i )
{
// 按位异或 说明portion[i]还没有分配完
if ( portion[i] ^ 0XFF )
{
// 先从portion[i]的最高位来分配 最高位在前面和buffer顺序对应
for ( int k = 7; k >= 0; --k )
{
// 先判断是否已经分配出去了
if ( portion[i] & (1 << k) )
{
continue;
}
--free_num;
portion[i] |= 1 << k; // 标记这个内存已分配出去了
return &buffer[(i*8+(7-k))*NODE_SIZE]; // 返回对应的buffer地址
}
}
}
return nullptr;
}
template<unsigned int _NODE_SIZE_, unsigned int _NODE_COUNT_>
bool MemBlock2<_NODE_SIZE_,_NODE_COUNT_>::Free( const void* p )
{
if ( free_num == NODE_COUNT )
{
#ifdef TRACE_MEM_BLOCK
PLT_Trace( "%s 内存块没有往外分配任何元素\n", __FUNCTION__ );
#endif
return false;
}
if ( (uintptr_t)p < (uintptr_t)&buffer[0] || (uintptr_t)p > (uintptr_t)&buffer[BUFFER_SIZE-1] )
{
#ifdef TRACE_MEM_BLOCK
PLT_Trace( "%s 指针并不是block分配出去的 buffer.begin=%p, buffer.end=%p p=%p\n", __FUNCTION__, &buffer[0], &buffer[BUFFER_SIZE-1], p );
#endif
return false;
}
if( ((uintptr_t)p - (uintptr_t)&buffer[0])%NODE_SIZE != 0 )
{
#ifdef TRACE_MEM_BLOCK
PLT_Trace( "%s 指针存在这个block上,但不在一个分配的位置 buffer.begin=%p, buffer.end=%p p=%p\n", __FUNCTION__, &buffer[0], &buffer[BUFFER_SIZE-1], p );
#endif
return false;
}
uintptr_t index = ((uintptr_t)p - (uintptr_t)&buffer[0])/NODE_SIZE;
if ( !( portion[index/8] & (1 << (7-index%8)) ) )
{
#ifdef TRACE_MEM_BLOCK
PLT_Trace( "%s 指针存在这个block上,但这个位置并没有分配出去 buffer.begin=%p, buffer.end=%p p=%p\n", __FUNCTION__, &buffer[0], &buffer[BUFFER_SIZE-1], p );
#endif
return false;
}
#ifdef TRACE_MEM_BLOCK
if ( memcmp( tracemem, tracememflag, sizeof(tracemem) ) != 0 )
{
PLT_Trace( "%s 内存块写超了\n", __FUNCTION__ );
}
#endif
++free_num;
portion[index/8] ^= (1 << (7-index%8)); // 还原已分配出去的标记
return true;
}
template<unsigned int _NODE_SIZE_, unsigned int _NODE_COUNT_>
void MemBlock2<_NODE_SIZE_,_NODE_COUNT_>::Free()
{
if ( free_num == NODE_COUNT )
{
return;
}
#ifdef TRACE_MEM_BLOCK
if ( memcmp( tracemem, tracememflag, sizeof(tracemem) ) != 0 )
{
PLT_Trace( "%s 内存块写超了\n", __FUNCTION__ );
}
#endif
free_num = NODE_COUNT;
memset( &portion[0], 0, sizeof(portion) );
}
template<unsigned int _NODE_SIZE_, unsigned int _NODE_COUNT_>
template<class T>
inline bool MemBlock2<_NODE_SIZE_,_NODE_COUNT_>::Delete( const void* p )
{
if ( free_num == NODE_COUNT )
{
#ifdef TRACE_MEM_BLOCK
PLT_Trace( "%s block没有往外分配任何元素\n", __FUNCTION__ );
#endif
return false;
}
if ( (uintptr_t)p < (uintptr_t)&buffer[0] || (uintptr_t)p > (uintptr_t)&buffer[BUFFER_SIZE-1] )
{
#ifdef TRACE_MEM_BLOCK
PLT_Trace( "%s 指针并不是block分配出去的 buffer.begin=%p, buffer.end=%p p=%p\n", __FUNCTION__, &buffer[0], &buffer[BUFFER_SIZE-1], p );
#endif
return false;
}
if( ((uintptr_t)p - (uintptr_t)&buffer[0])%NODE_SIZE != 0 )
{
#ifdef TRACE_MEM_BLOCK
PLT_Trace( "%s 指针存在这个block上,但不在一个分配的位置 buffer.begin=%p, buffer.end=%p p=%p\n", __FUNCTION__, &buffer[0], &buffer[BUFFER_SIZE-1], p );
#endif
return false;
}
uintptr_t index = ((uintptr_t)p - (uintptr_t)&buffer[0])/NODE_SIZE;
if ( !( portion[index/8] & (1 << (7-index%8)) ) )
{
#ifdef TRACE_MEM_BLOCK
PLT_Trace( "%s 指针存在这个block上,但这个位置并没有分配出去 buffer.begin=%p, buffer.end=%p p=%p\n", __FUNCTION__, &buffer[0], &buffer[BUFFER_SIZE-1], p );
#endif
return false;
}
#ifdef TRACE_MEM_BLOCK
if ( memcmp( tracemem, tracememflag, sizeof(tracemem) ) != 0 )
{
PLT_Trace( "%s 内存块写超了\n", __FUNCTION__ );
}
#endif
T* pobj = (T*)p;
pobj->~T(); // 这里相当于有回调 可能会存在在回调中调用同一个内存块创建和删除node
// 其实没关系 只要在这个回调中不重复删除自己(pobj)(若在析构函数中在删除自己 系统提供的delete也会崩掉) 就不会存在任何问题
// 因为本函数中只有下面的代码会修改这个内存块的变量
if ( free_num == NODE_COUNT ) // 防止在析构回调中调用了那个无参的Delete
{
#ifdef TRACE_MEM_BLOCK
PLT_Trace( "%s 出现了不可预知的错误\n", __FUNCTION__ );
#endif
return false;
}
++free_num;
portion[index/8] ^= (1 << (7-index%8)); // 还原已分配出去的标记
return true;
}
template<unsigned int _NODE_SIZE_, unsigned int _NODE_COUNT_>
template<class T>
inline void MemBlock2<_NODE_SIZE_,_NODE_COUNT_>::Delete()
{
if ( free_num == NODE_COUNT )
{
return;
}
#ifdef TRACE_MEM_BLOCK
if ( memcmp( tracemem, tracememflag, sizeof(tracemem) ) != 0 )
{
PLT_Trace( "%s 内存块写超了\n", __FUNCTION__ );
}
#endif
// 首先修改free_num 防止下面的析构函数再次使用内存块分配和释放node
free_num = NODE_COUNT;
unsigned char portion_temp[PORTION_SIZE];
memcpy( portion_temp, portion, sizeof(portion_temp) );
memset( &portion[0], 1, sizeof(portion) );
for ( int i = 0; i < PORTION_SIZE; ++i )
{
if ( portion_temp[i] )
{
for ( int k = 7; k >= 0; --k )
{
if ( portion_temp[i] & (1 << k) )
{
T* pobj = (T*)(&buffer[(i*8+(7-k))*NODE_SIZE]);
pobj->~T();
}
}
}
}
memset( &portion[0], 0, sizeof(portion) );
free_num = NODE_COUNT;
}
template<unsigned int _NODE_SIZE_, unsigned int _NODE_COUNT_>
inline bool MemBlock2<_NODE_SIZE_,_NODE_COUNT_>::Is_From( const void* p ) const
{
if ( (uintptr_t)p < (uintptr_t)&buffer[0] || (uintptr_t)p > (uintptr_t)&buffer[BUFFER_SIZE-1] )
{
return false;
}
if( ((uintptr_t)p - (uintptr_t)&buffer[0])%NODE_SIZE != 0 )
{
#ifdef TRACE_MEM_BLOCK
PLT_Trace( "%s 指针存在这个block上,但不在一个分配的位置 buffer.begin=%p, buffer.end=%p p=%p\n", __FUNCTION__, &buffer[0], buffer[BUFFER_SIZE-1], p );
#endif
return false;
}
if ( free_num == NODE_COUNT )
{
#ifdef TRACE_MEM_BLOCK
PLT_Trace( "%s 内存块没有往外分配任何元素\n", __FUNCTION__ );
#endif
return false;
}
uintptr_t index = ((uintptr_t)p - (uintptr_t)&buffer[0])/NODE_SIZE;
if ( !( portion[index/8] & (1 << (7-index%8)) ) )
{
#ifdef TRACE_MEM_BLOCK
PLT_Trace( "%s 指针存在这个block上,但这个位置并没有分配出去 buffer.begin=%p, buffer.end=%p p=%p\n", __FUNCTION__, &buffer[0], &buffer[BUFFER_SIZE-1], p );
#endif
return false;
}
return true;
}
}
#endif
简单测试下两种内存块及系统new的效率,申请内存块的元素为10000,循环1百万次,每次随机一个[0-10000)的索引,若此处申请过就销毁,若没申请就申请一个元素,测试条件是在Windows Release模式下,关闭掉内存块的追踪部分,测试代码如下:
int allnum = 0;
struct MyTest
{
MyTest( int i )
: num(i)
{
++allnum;
}
MyTest()
: num(1)
{
++allnum;
}
~MyTest()
{
--allnum;
}
int num;
};
void Test()
{
MyTest* allp[10000] = {0};
{
PLT_Speed_TestM( 0, "new" );
Palantir::MemBlock<sizeof(MyTest),10000> mem;
for ( int i = 0; i < 1000000; ++i )
{
int index = rand()%10000;
if ( index == 100 )
{
for ( int k = 0; k < 10000; ++k )
{
if ( allp[k] )
{
delete allp[k];
}
}
memset( allp, 0, sizeof(allp) );
continue;;
}
if ( allp[index] )
{
delete allp[index];
allp[index] = nullptr;
}
else
{
allp[index] = new MyTest(index);
}
}
for ( int k = 0; k < 10000; ++k )
{
if ( allp[k] )
{
delete allp[k];
}
}
// 清空
memset( allp, 0, sizeof(allp) );
}
{
PLT_Speed_TestM( 0, "mem" );
Palantir::MemBlock<sizeof(MyTest),10000> mem;
for ( int i = 0; i < 10; ++i )
{
int index = rand()%10000;
if ( index == 100 )
{
mem.Delete<MyTest>();
memset( allp, 0, sizeof(allp) );
continue;;
}
if ( allp[index] )
{
mem.Delete<MyTest>(allp[index]);
allp[index] = nullptr;
}
else
{
allp[index] = mem.New<MyTest>(index);
}
}
mem.Delete<MyTest>();
// 清空
memset( allp, 0, sizeof(allp) );
}
{
PLT_Speed_TestM( 0, "mem2" );
Palantir::MemBlock2<sizeof(MyTest),10000> mem2;
for ( int i = 0; i < 1000000; ++i )
{
int index = rand()%10000;
if ( index == 100 )
{
mem2.Delete<MyTest>();
memset( allp, 0, sizeof(allp) );
continue;;
}
if ( allp[index] )
{
mem2.Delete<MyTest>(allp[index]);
allp[index] = nullptr;
}
else
{
allp[index] = mem2.New<MyTest>(index);
}
}
mem2.Delete<MyTest>();
// 清空
memset( allp, 0, sizeof(allp) );
}
}
测试结果如下:
SpeedTest usetime: 97(sec) 790(milli) 0(micro) Test: new
SpeedTest usetime: 26(sec) 695(milli) 0(micro) Test: mem
SpeedTest usetime: 0(sec) 215(milli) 0(micro) Test: mem2
显然两种内存块都比系统的快,且mem2基本上比系统函数快400倍,mem比系统函数快4倍,mem就是慢在Delete函数里了,因为每次删除都要遍历下那些没有分配出去的node,但基本不会比系统多使用内存,但mem2基本上是每8000个元素就要多出1m的内存,空间换效率