博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
转 内存池技术的原理与实现
阅读量:7260 次
发布时间:2019-06-29

本文共 8070 字,大约阅读时间需要 26 分钟。

 

序言

  最近在网上看到了几篇篇讲述内存池技术的文章,有一篇是有IBM中国研发中心的人写的,写的不错~~文章地址在本篇blog最后。原文的讲述比我的要清晰很多,我在这只是把我的一些理解和遇到的一些问题和大家分享一下~~

一、为什么要使用内存池技术呢

  主要有两个原因:1、减少new、delete次数,减少运行时间;2、避免内存碎片。

  1、效率

  c语言中使用malloc/free来分配内存,c++中使用new/delete来分配内存,他们的内存申请与释放都是与操作系统进行交互的。具体的内容在严蔚敏数据结构的第八章有相关讲述,主要就是系统要维护一个内存链表,当有一个内存申请过来时,根据相应的分配算法在链表中找个一个合适的内存分配给它。这些算法有的是分配最先找到的不小于申请内存的内存块,有的是分配最大的内存块,有的是分配最接近申请内存大小的内存块。分配的内存块可能会大于所申请的内存大小,这样还有进行切割,将剩余的内存插入到空闲链表中。当释放的时候,系统可能要对内存进行整理,判断free的内存块的前后是否有空闲,若有的话还要进行合并。此外,new/delete还要考虑多线程的情况。总之一句话,调用库中的内存分配函数,十分的耗时~~

  2、内存碎片

  什么是内存碎片内,从字面意思就很好理解了,就是内存不再是一整块的了,而是碎了。因为连续的这种new/delete操作,一大块内存肯能就被分割成小的内存分配出去了,这些小的内存都是不连续的。当你再去分配大的连续内存的时候,尽管剩余内存的总和可能大于所要分配的内存大小,但系统就找不到连续的内存了,所以导致分配错误。malloc的时候会导致返回NULL,而new的时候再vc6.0中返回NULL,vs2003以上则是抛出异常。

二、原理

  要解决上述两个问题,最好的方法就是内存池技术。具体方法就是大小固定、提前申请、重复利用。

  因为内存的申请和释放是很低效的,所以我们只在开始时申请一块大的内存(在该块内存不够用时在二次分配),然后每次需要时都从这块内存中取出,并标记下这块内存被用了,释放时标记此内存被释放了。释放时,并不真的把内存释放给操作系统,只要在一大块内存都空闲的时候,才释放给操作系统。这样,就减少了new/delete的操作次数,从而提高了效率。

  在调用内存分配函数的时候,大部分时间所分配的内存大小都是一定的,所以可以采用每次都分配固定大小的内存块,这样就避免了内存碎片产生的可能。

三、具体实现

  我所采用的内存池的构造方法完全是按照所介绍的方法,内存池的结构图如下:

  

   如图所示MemoryPool是一个内存池类,其中pBlock是一个指向了一个内存块的指针,nUintSzie是分配单元的大小,nInitSize是第一次分配时向系统申请的内存的大小,nGrouSize是后面每次向系统申请的内存的大小。

  MemoryBloc代表一个内存块单元,它有两部分构成,一部分时MemoryBlock类的大小,另一部分则是实际的内存部分。一个MemoryBlock的内存是在重载的new操作符中分配的,如下所示: 

void
* MemoryBlock::operator
new
(
size_t
,
int 
nUnitSize,
int 
nUnitAmount )
{
    
return 
::operator
new
(
sizeof
(MemoryBlock) + nUnitSize * nUnitAmount );
}

    MemoryBlock内中,nSize代码该内存块的大小(系统分配内存大小-MemoryBlock类的大小),nFree是空闲内存单元的个数,nFirst代表的是下一个要分配的内存单元的序号。aData是用来记录待分配内存的位置的。因为要分配的内存是在new中一起向系统申请的,并没有一个指针指向这块内存的位置,但它的位置就在MemoryBlock这个类的地址开始的,所以可以用MemoryBlock的最后一个成员的位置来表示待分配内存的位置。

  带分配内存中,是以nUnitSize为单位的,一个内存单元的头两个字节都记录了下一个要分配的内存单元的序号,序号从0开始。这样实际也就构成了一个数组链表。由MemoryBlock的构造函数来完成这个链表的初始化工作:

MemoryBlock::MemoryBlock(
int 
nUnitSize,
int 
nUnitAmount )
    
:   nSize   (nUnitAmount * nUnitSize),
        
nFree   (nUnitAmount - 1), 
//构造的时候,就已将第一个单元分配出去了,所以减一
        
nFirst  (1),               
//同上
        
pNext   (NULL)
{
    
//初始化数组链表,将每个分配单元的下一个分配单元的序号写在当前单元的前两个字节中
    
char
* pData = aData;
    
//最后一个位置不用写入
    
for
(
int 
i = 1; i < nSize - 1; i++)
    
{
        
(*(
USHORT
*)pData) = i;
        
pData += nUnitSize;
    
}
}

  

  在MemoryPool的Alloc()中,遍历block链表,找到nFree大于0的block,从其上分配内存单元。然后将nFree减一,修改nFirst的值。

  在MemoryPool的Free(pFree)函数中,根据pFree的值,找到它所在的内存块,然后将它的序号作为nFirst的值(因为它绝对是空闲的),在pFree的头两个字节中写入原来nFirst的值。然后要判断,该block是否全部为free,方法是检测nFree * nUnitSize == nSize。若是,则向系统释放内存,若不是,则将该block放到链表的头部,因为该block上一定含有空隙的内存单元,这样可以减少分配时遍历链表所消耗的时间。

四、使用

  内存池一般都是作为一个类的静态成员,或者全局变量。使用时,重载new操作符,使其到MemoryPool中去分配内存,而不是向系统申请。这样,一个类的所以对象都在一个内存池中开辟空间。

void 
CTest::operator
delete
(
void
* pTest )
{  
    
Pool.Free(pTest);  
}
 
 
void
* CTest::operator
new
(
size_t
)
{
    
return 
(CTest*)Pool.Alloc();
}

   

五、代码

MemoryPool.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdlib.h>
#include <wtypes.h>
 
#define  MEMPOOL_ALIGNMENT 8            //对齐长度
//内存块,每个内存块管理一大块内存,包括许多分配单元
class 
MemoryBlock
{
public
:
                    
MemoryBlock (
int 
nUnitSize,
int 
nUnitAmount);
                    
~MemoryBlock(){};
    
static 
void
*    operator 
new    
(
size_t
,
int 
nUnitSize,
int 
nUnitAmount);
    
static 
void     
operator 
delete 
(
void
* ,
int 
nUnitSize,
int 
nUnitAmount){};
    
static 
void     
operator 
delete 
(
void
* pBlock);
 
    
int             
nSize;              
//该内存块的大小,以字节为单位
    
int             
nFree;              
//该内存块还有多少可分配的单元
    
int             
nFirst;             
//当前可用单元的序号,从0开始
    
MemoryBlock*    pNext;              
//指向下一个内存块
    
char            
aData[1];           
//用于标记分配单元开始的位置,分配单元从aData的位置开始
     
};
 
class 
MemoryPool
{
public
:
                    
MemoryPool (
int 
_nUnitSize,
                                
int 
_nGrowSize = 1024,
                                
int 
_nInitSzie = 256);
                    
~MemoryPool();
    
void
*           Alloc();
    
void            
Free(
void
* pFree);
 
private
:
    
int             
nInitSize;          
//初始大小
    
int             
nGrowSize;          
//增长大小
    
int             
nUnitSize;          
//分配单元大小
    
MemoryBlock*    pBlock;             
//内存块链表
};

 MemoryPool.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include "MemoryPool.h"
 
MemoryBlock::MemoryBlock( 
int 
nUnitSize,
int 
nUnitAmount )
    
:   nSize   (nUnitAmount * nUnitSize),
        
nFree   (nUnitAmount - 1),  
//构造的时候,就已将第一个单元分配出去了,所以减一
        
nFirst  (1),                
//同上
        
pNext   (NULL)
{
    
//初始化数组链表,将每个分配单元的下一个分配单元的序号写在当前单元的前两个字节中
    
char
* pData = aData;
    
//最后一个位置不用写入
    
for
int 
i = 1; i < nSize - 1; i++)
    
{
        
(*(
USHORT
*)pData) = i;
        
pData += nUnitSize;
    
}
}
 
void
* MemoryBlock::operator 
new
(
size_t
int 
nUnitSize,
int 
nUnitAmount )
{
    
return 
::operator 
new
sizeof
(MemoryBlock) + nUnitSize * nUnitAmount );
}
 
void 
MemoryBlock::operator 
delete
void
* pBlock)
{
    
::operator 
delete
(pBlock);
}
 
MemoryPool::MemoryPool( 
int 
_nUnitSize, 
int 
_nGrowSize 
/*= 1024*/
int 
_nInitSzie 
/*= 256*/ 
)
{
    
nInitSize = _nInitSzie;
    
nGrowSize = _nGrowSize;
    
pBlock = NULL;
    
if
(_nUnitSize > 4)
        
nUnitSize = (_nUnitSize + (MEMPOOL_ALIGNMENT - 1)) & ~(MEMPOOL_ALIGNMENT - 1);
    
else 
if
( _nUnitSize < 2)
        
nUnitSize = 2;
    
else
        
nUnitSize = 4;
}
 
MemoryPool::~MemoryPool()
{
    
MemoryBlock* pMyBlock = pBlock;
    
while
( pMyBlock != NULL)
    
{
        
pMyBlock = pMyBlock->pNext;
        
delete
(pMyBlock);
    
}
}
 
void
* MemoryPool::Alloc()
{
    
if
( NULL == pBlock)
    
{
        
//首次生成MemoryBlock,new带参数,new了一个MemoryBlock类
        
pBlock = (MemoryBlock*)
new
(nUnitSize,nInitSize) MemoryBlock(nUnitSize,nUnitSize);
        
return 
(
void
*)pBlock->aData;
    
}
 
    
//找到符合条件的内存块
    
MemoryBlock* pMyBlock = pBlock;
    
while
( pMyBlock != NULL && 0 == pMyBlock->nFree )
        
pMyBlock = pMyBlock->pNext;
 
    
if
( pMyBlock != NULL)
    
{
        
//找到了,进行分配
        
char
* pFree = pMyBlock->aData + pMyBlock->nFirst * nUnitSize;
        
pMyBlock->nFirst = *((
USHORT
*)pFree);
        
pMyBlock->nFree--;
 
        
return 
(
void
*)pFree;
    
}
    
else
    
{
        
//没有找到,说明原来的内存块都满了,要再次分配
 
        
if
( 0 == nGrowSize)
            
return 
NULL;
         
        
pMyBlock = (MemoryBlock*)
new
(nUnitSize,nGrowSize) MemoryBlock(nUnitSize,nGrowSize);
 
        
if
( NULL == pMyBlock)
            
return 
NULL;
 
        
//进行一次插入操作
        
pMyBlock->pNext = pBlock;
        
pBlock = pMyBlock;
 
        
return 
(
void
*)pMyBlock->aData;
    
}
}
 
void 
MemoryPool::Free( 
void
* pFree )
{
    
//找到p所在的内存块
    
MemoryBlock* pMyBlock = pBlock;
    
MemoryBlock* PreBlock = NULL;
    
while 
( pMyBlock != NULL && ( pBlock->aData > pFree || pMyBlock->aData + pMyBlock->nSize))
    
{
        
PreBlock = pMyBlock;
        
pMyBlock = pMyBlock->pNext;
    
}
 
    
if
( NULL != pMyBlock )      
//该内存在本内存池中pMyBlock所指向的内存块中
    
{      
        
//Step1 修改数组链表
        
*((
USHORT
*)pFree) = pMyBlock->nFirst;
        
pMyBlock->nFirst  = (
USHORT
)((
ULONG
)pFree - (
ULONG
)pMyBlock->aData) / nUnitSize;
        
pMyBlock->nFree++;
 
        
//Step2 判断是否需要向OS释放内存
        
if
( pMyBlock->nSize == pMyBlock->nFree * nUnitSize )
        
{
            
//在链表中删除该block
             
            
delete
(pMyBlock);
        
}
        
else
        
{
            
//将该block插入到队首
            
PreBlock = pMyBlock->pNext;
            
pMyBlock->pNext = pBlock;
            
pBlock = pMyBlock;
        
}
    
}
}

 CTest.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include "MemoryPool.h"
 
class 
CTest
{
public
:
                
CTest(){data1 = data2 = 0;};
                
~CTest(){};
    
void
*       operator 
new 
(
size_t
);
    
void        
operator 
delete
(
void
* pTest);
public
:
 
    
static      
MemoryPool Pool;
    
int         
data1;
    
int         
data2;
};
 
void 
CTest::operator 
delete
void
* pTest )
{  
    
Pool.Free(pTest);  
}
 
 
void
* CTest::operator 
new
(
size_t
)
{
    
return 
(CTest*)Pool.Alloc();
}
 
MemoryPool CTest::Pool(
sizeof
(CTest));
 
int 
main()
{
    
CTest* pTest = 
new 
CTest;
    
printf
(
"%d"
,pTest->data2);
}

六、问题

  在编写代码时,遇到了一些小问题,现与大家分享如下:

  1、重载new操作符时,编译器要求是第一个参数必须是size_t,返回值必须是void*;free的第一个参数必须是void*.

  2、一般要在类的成员中重载new操作符,而不要重载全局的new操作符。

  3、一个类中要是重载了一个new操作符,一定要有一个相应类型的delete操作符,可以什么都不干,但必须有,否则在构造函数失败时,找不到对应的delete函数。

例如:  

1
2
static 
void
*    operator 
new    
(
size_t
,
int 
nUnitSize,
int 
nUnitAmount);
    
static 
void     
operator 
delete 
(
void
* ,
int 
nUnitSize,
int 
nUnitAmount){};

  4、带参数的new操作符

pBlock = (MemoryBlock*)
new
(nUnitSize,nInitSize) MemoryBlock(nUnitSize,nUnitSize);

  第一个nUnitSize nInitSize是new操作符的参数,该new操作符是new了一个MemoryBlock对象,在new返回的地址上构造MemoryBlock的对象。

  5、如果在类的内部不能进行静态成员的定义的话,可以只在内部进行声明,在外部定义:

MemoryPool CTest::Pool(
sizeof
(CTest));

  

------------------------------------------------------------END----------------------------------------------------------------------

文章1:;

文章2:;

转载于:https://www.cnblogs.com/njczy2010/p/5867803.html

你可能感兴趣的文章
将多个文件夹内的txt合并
查看>>
Jquery — fancybox
查看>>
Linux shell笔记
查看>>
LINQ学习笔记(7) 标准查询运算符(上)
查看>>
分配虚拟化磁盘路径的优先级
查看>>
Django未来将支持Python3
查看>>
分享32款令人惊叹的文字信息图
查看>>
Robot Framework安装指南
查看>>
vc++ 6.0对话框上无法显示中文(乱码)
查看>>
VC中字符串由于版本不同而导致的错误问题
查看>>
将Xml字符串转换成(DataTable || DataSet || XML)对象
查看>>
Android用Application设置全局变量以及使用
查看>>
android-远程图片获取和本地缓存
查看>>
C/C++中各种类型int、long、double、char表示范围(最大最小值)
查看>>
MySQL的一些基本操作
查看>>
InstallShield 12 制作安装包
查看>>
Option Handling
查看>>
批量另存mxd
查看>>
[android] 看博客学习Android常见的几种RuntimeException
查看>>
分布式缓存技术redis学习(一)——redis简介以及linux上的安装
查看>>