讨论QQ群:135202158
在一些应用场合,比如一个繁忙的服务器程序中,服务器频繁地接受并处理用户请求,如果每次处理都需要从系统动态地申请和释放内存,由于此操作开销较大,会加大处理时延;另一方面,此开销造成的累积效应也是很大的。
对此我们可以举一个生活中的例子:假设一个烟鬼一天抽10包烟(这有点太多了,假设啊),如果他每次去商店只买一包烟,他一天就要往商店跑10次,很麻烦;如果他一次买一条(10 包)放着,那么他一天之内就可以不用再跑商店了,想抽烟的时候,直接拿抽剩下的就行了。
这种预先分配所有所需资源、在运行时直接调配的思想,都可以称为池化策略,比如内存池和线程池。其实上面烟鬼买烟的例子,举的并不是特别恰当。因为池中的资源(内存或线程),在使用完之后,是可以回收的,而烟抽完就没有了。
这篇随笔主要讲内存池,线程池见http://www.cnblogs.com/zzqcn/p/3625452.html。
要实现内存池,除了预先分配一块大内存块之外,我们还需要设计一个用来记录当前内存使用情况的数据结构,它至少需要记录以下信息:
- 某个内存块是否可用
- 内存块的起始地址
- 内存块的大小
我们可以设计一个双向循环链表来保存内存使用信息,而表中每一个结点如下图所示:
如果用C++语言表示,则如下(mbl:memory block list):
struct mblnode { mblnode* left; mblnode* right; bool tag; size_t offset; size_t size; };
另外,还需要制订内存分配和回收的策略。这些策略包括:
- 从哪个地方开始分配
- 用户申请多少,就分配多少,还是有一个分配最小值
- 回收某个内存块之后,如何和其他空闲块合并成大的空闲块
- 回收内存块之后,下次分配的位置是否变化
讨论分配和策略之前,我们先设定一个当前指针p,它指向一个可用块,每次内存分配都从这个可用块进行。
对于1:每次分配后,都将p指向已分配块的下一个可用内存块;
对于2:设定一个最小值MINSIZE,当用户申请的内存块大小小于MINSIZE时,就分配MINSIZE大小的内存块给用户;
对于3:回收某块内存后,需要根据多种不同情况,与周围的可用块进行合并,形成更大的可用块;
对于4:回收内存后,应比较新合并的可用块的大小和p指向的内存块的大小,并将p指向更大的内存块。
上述分配回收策略,都减小了内存池中的碎片,提升分配效率。
下面用图演示一下分配过程。假设内存池大小为12,绿色代表空闲块,红色代表已用块,p为当前分配指针,下面的数据结构是我们的双向循环链表,链表结点中第1项是是否可用的标志,打勾表示可用,打x表示已用,第2项表示内存块起始地址,第3项表示此块的大小。下图表示从完全空闲的内存池连续分配大小为2、5的两块内存的情况:
下图表示回收一块内存时与周围空闲块合并的多种情况,每一种情况都需要小心地编码:
完整C++代码:
1 /* 2 * 内存池实现 - 使用双循环链表. 3 * Author: 赵子清 4 * Blog: http://www.cnblogs.com/zzqcn 5 **/ 6 7 8 #include <cstdlib> 9 #include <cstdio> 10 11 12 struct mblnode 13 { 14 mblnode* left; 15 mblnode* right; 16 bool tag; 17 size_t offset; 18 size_t size; 19 }; 20 21 22 #define MAXBUFF 12 23 #define MINSIZE 2 24 25 char BUFF[MAXBUFF]; 26 mblnode* mbl = NULL; 27 28 int my_init(); 29 int my_deinit(); 30 char* my_new(size_t _n); 31 int my_del(char* _p); 32 void my_merge_nil (mblnode* _p); 33 void my_merge_left (mblnode* _p); 34 void my_merge_right(mblnode* _p); 35 void my_merge_both (mblnode* _p); 36 int my_print_mbl(); 37 38 39 int main(int argc, char** argv) 40 { 41 my_init(); 42 43 // new 44 char* p = my_new(2); 45 if (p != NULL) 46 { 47 p[0] = 'a'; 48 p[1] = 'b'; 49 } 50 my_print_mbl(); 51 52 // new 53 char* p2 = my_new(5); 54 if (p2 != NULL) 55 { 56 p2[0] = 'c'; 57 p2[1] = 'd'; 58 p2[2] = 'e'; 59 p2[3] = 'f'; 60 p2[4] = 'g'; 61 } 62 my_print_mbl(); 63 64 // new 65 char* p3 = my_new(2); 66 if (p3 != NULL) 67 { 68 p3[0] = 'h'; 69 p3[1] = 'i'; 70 } 71 my_print_mbl(); 72 73 my_del(p2); my_print_mbl(); 74 my_del(p); my_print_mbl(); 75 my_del(p3); my_print_mbl(); 76 77 my_deinit(); 78 79 system("PAUSE"); 80 return 0; 81 } 82 83 84 int my_init() 85 { 86 if (mbl != NULL) 87 my_deinit(); 88 89 mblnode* p = new mblnode; 90 p->left = p->right = p; 91 p->offset = 0; 92 p->size = MAXBUFF; 93 p->tag = false; 94 95 mbl = p; 96 97 return 0; 98 } 99 100 101 int my_deinit() 102 { 103 if (NULL == mbl) 104 return 0; 105 106 mblnode* p = NULL; 107 mblnode* q = NULL; 108 109 p = mbl; 110 do 111 { 112 q = p; 113 p = p->right; 114 delete q; q = NULL; 115 } while (p != mbl); 116 117 return 0; 118 } 119 120 121 char* my_new(size_t _n) 122 { 123 mblnode* p = NULL; 124 char* ret = NULL; 125 126 if (NULL == mbl) 127 return NULL; 128 129 if (_n < MINSIZE) 130 _n = MINSIZE; 131 132 for (p = mbl; p->right != mbl; p = p->right) 133 { 134 if (p->tag || p->size < _n) 135 continue; 136 else 137 break; 138 } 139 140 if (NULL == p || p->size < _n) 141 return NULL; 142 143 if (p->size > _n) 144 { 145 mblnode* pnew = new mblnode; 146 p->left->right = pnew; 147 pnew->left = p->left; 148 pnew->right = p; 149 pnew->offset = p->offset; 150 pnew->size = _n; 151 pnew->tag = true; 152 153 p->size -= _n; 154 p->offset += _n; 155 p->left = pnew; 156 157 mbl = p; 158 ret = BUFF + pnew->offset; 159 } 160 else if (p->size == _n) 161 { 162 p->tag = true; 163 164 mbl = p; 165 ret = BUFF + p->offset; 166 } 167 168 return ret; 169 } 170 171 172 int my_del(char* _p) 173 { 174 if (NULL == _p || _p < BUFF || _p >= BUFF + MAXBUFF) 175 return -1; 176 177 int ret = -1; 178 mblnode* p = NULL; 179 mblnode* pcur = NULL; 180 181 p = mbl; 182 do 183 { 184 if ((p->offset + BUFF) == _p) 185 { 186 pcur = p; 187 break; 188 } 189 p = p->right; 190 } while (p != mbl); 191 192 193 if (NULL == pcur) 194 return -1; 195 196 /* left & right are all NOT free */ 197 if (pcur->left->tag && pcur->right->tag) 198 { 199 my_merge_nil(pcur); 200 ret = 0; 201 } 202 /* left is free */ 203 else if (!pcur->left->tag && pcur->right->tag) 204 { 205 if (pcur->offset != 0) 206 my_merge_left(pcur); 207 /* upper boundary */ 208 else 209 my_merge_nil(pcur); 210 211 ret = 0; 212 } 213 /* right is free */ 214 else if (pcur->left->tag && !pcur->right->tag) 215 { 216 if (pcur->right->offset != 0) 217 my_merge_right(pcur); 218 /* lower boundary */ 219 else 220 my_merge_nil(pcur); 221 222 ret = 0; 223 } 224 /* left and right are all free */ 225 else if (!pcur->left->tag && !pcur->right->tag) 226 { 227 //if (0 == pcur->left->offset) 228 if (0 == pcur->offset) 229 my_merge_right(pcur); 230 else if (0 == pcur->right->offset) 231 my_merge_left(pcur); 232 else 233 my_merge_both(pcur); 234 235 ret = 0; 236 } 237 238 return ret; 239 } 240 241 242 void my_merge_nil(mblnode* _p) 243 { 244 _p->tag = false; 245 if (mbl->size < _p->size) 246 mbl = _p; 247 } 248 249 void my_merge_left(mblnode* _p) 250 { 251 _p->left->right = _p->right; 252 _p->right->left = _p->left; 253 _p->left->size += _p->size; 254 255 if (mbl->size < _p->left->size) 256 mbl = _p->left; 257 258 delete _p; _p = NULL; 259 } 260 261 void my_merge_right(mblnode* _p) 262 { 263 _p->left->right = _p->right; 264 _p->right->left = _p->left; 265 _p->right->offset = _p->offset; 266 _p->right->size += _p->size; 267 268 if (mbl->size < _p->right->size) 269 mbl = _p->right; 270 271 delete _p; _p = NULL; 272 } 273 274 void my_merge_both(mblnode* _p) 275 { 276 _p->left->right = _p->right->right; 277 _p->right->right->left = _p->left; 278 279 _p->left->size += _p->size + _p->right->size; 280 281 if (mbl->size < _p->left->size) 282 mbl = _p->left; 283 284 delete _p->right; _p->right = NULL; 285 delete _p; _p = NULL; 286 } 287 288 289 int my_print_mbl() 290 { 291 if (NULL == mbl) 292 return -1; 293 294 mblnode* p = mbl; 295 296 do 297 { 298 printf("[%c, %d, %d]\n", 299 p->tag ? 'x' : 'v', 300 p->offset, 301 p->size); 302 p = p->right; 303 } while (p != mbl); 304 305 printf("------------------\n"); 306 307 return 0; 308 }
输出结果:
[v, 2, 10] [x, 0, 2] ------------------ [v, 7, 5] [x, 0, 2] [x, 2, 5] ------------------ [v, 9, 3] [x, 0, 2] [x, 2, 5] [x, 7, 2] ------------------ [v, 2, 5] [x, 7, 2] [v, 9, 3] [x, 0, 2] ------------------ [v, 0, 7] [x, 7, 2] [v, 9, 3] ------------------ [v, 0, 12] ------------------ 请按任意键继续. . .
【参考资料】
《数据结构(C语言版)》,严蔚敏 吴伟民 编著,清华大学出版社。