多进程编程多用在并发服务器的编写上,当收到一个请求时,服务器新建一个进程处理请求,同时继续监听。为了提高响应速度,服务器采用进程池的方法,在初始化阶段创建一个进程池,池中有许多预创建的进程,当请求到达时,只需从池中分配出来一个进程即可;当进程不够用时,进程池将再次创建一批进程。类似的方法可以用在内存分配上。

C++中,创建一个复杂的对象需要几十条指令,包括函数调用的代价(寄存器值得保存和恢复),以及构造或复制构造函数体的执行代价,甚至动态分配内存的代价。尤其是,在不重载new和delete运算符时,由于C++的new和delete是通用的,其中甚至提供了支持多线程编程的同步机制,对于单线程编程将带来不必要的运算。

如果需要创建的对象的个数是动态变化的,可以首先预开辟一片较大的内存,每次要创建对象时将一段内存分配出去。为了提高创建对象的速度,内存池应满足如下设计要求:

1,一定的通用性,适用于多类型,而不局限于某个类;

2,一定的灵活性,可以在不对类作过多修改的前提下,将内存池机制轻易融入到原代码中;

3,可能要满足多线程的要求。

4,通用性、灵活性视实际情况而定,不必完美。

一、专用内存池

先从专用内存池开始,假设要不断创建Complex (复数)类,下面的代码实现了针对Complex对象的内存池。看过《Efficient C++》(不是Effective C++)中关于内存池的内容后,笔者打算自己耍一耍:

#include <iostream>
#include <time.h>
using namespace std; class SimpleComplex
{
double re;
double im; public:
explicit SimpleComplex(const double r = 0.0, const double i = 0.0):re(r), im(i) {} }; class Next
{
public:
Next * next;
}; class Complex
{
static Next * freelist;
enum {EXPAND_SIZE = }; double re;
double im; static void expand()
{
Next * p = (Next *) (new char [sizeof(Complex)]);
freelist = p;
for(int i=; i<EXPAND_SIZE; i++)
{
p->next = (Next *) new char [sizeof(Complex)];
p = p->next;
}
p->next = ;
} public:
explicit Complex(const double r = 0.0, const double i = 0.0):re(r), im(i) {}
inline void * operator new (size_t size)
{
if ( == freelist)
expand();
Next * p = freelist;
freelist = freelist->next;
return p;
}
inline void operator delete(void * ptr, size_t size)
{
((Next *)ptr)->next = freelist;
freelist = (Next *)ptr;
}
static void newPool()
{
expand();
}
static void deletePool()
{
Next * p = freelist;
while (p)
{
freelist = freelist->next;
delete [] p;
p = freelist;
}
}
}; Next * Complex::freelist = ; main()
{
double from = time();
Complex *c[];
for (int i=; i<; i++)
{
for (int j=; j<; j++)
c[j] = new Complex();
for (int j=; j<; j++)
delete c[j];
}
Complex::deletePool();
double to = time();
cout <<to-from<<endl; from = time();
SimpleComplex *s[];
for (int i=; i<; i++)
{
for (int j=; j<; j++)
s[j] = new SimpleComplex();
for (int j=; j<; j++)
delete s[j];
}
to = time();
cout <<to-from<<endl;
}

注意虽然有一个class next,但总体上看所有的内存块却并不组成链表。这里,内存是这么分配的:任何时刻freelist指向可用的内存块链表的头部,即第一个可用的内存块(单不足时,freelist指向NULL)。假设分别执行下面的语句:

p1 = new Complex ();
p2 = new Complex ();
p3 = new Complex ();
p4 = new Complex ();
delete p2;
delete p4;
p5 = new Complex ();

过程是这样的。Complex专属内存池在初始时刻不分配任何空间。给p1创建对象时,由于freelist指向NULL,先按照EXPAND_SIZE的值,开辟EXPAND_SIZE * sizeof(Complex)大小的内存。此时:

freelist -> [      next] -> [     next] -> NULL

随后将freelist指向的内存块分配给p1:

p1-> [      next]       freelist-> [     next] -> NULL

随后将freelist指向的内存块分配给p2:

p1-> [      next]       p2-> [     next]      freelist -> NULL

随后为p3分配内存,在此之前再次执行expand() :

p1-> [      next]       p2 -> [     next]     p3 -> [    next]     freelist -> [    next] -> NULL

随后将freelist指向的内存块分配给p4:

p1-> [      next]       p2 -> [     next]     p3 -> [    next]     p4 -> [    next]      freelist-> NULL

随后释放p2,内存池这时需要将p2的空间回收再利用,于是把p2的空间插入到freelist指向的链的前端:

p1-> [      next]       p3 -> [    next]      p4 -> [    next]     freelist -> [(p2) next]   -> NULL

随后释放p2,内存池这时需要将p2的空间回收再利用,于是把p2的空间插入到freelist指向的链的前端:

p1-> [      next]       p3 -> [    next]     freelist -> [(p4) next] -> [(p2) next]   -> NULL

这样为p5开辟内存时,只需将原p4的内存给p5即可。

虽然池中的内存块不是以链表形式存在,当上述代码保证每个内存块都有一个指针变量指向它,最终在销毁时可以正常销毁。当然,如果仅仅写道

new Complex ();

这时创建的内存块就成了碎片,就算对于通用的new delete操作符而言,这样的碎片也是无可奈何的。

注意,内存池并不实现像Java和C#这样自动回收内存的复杂机制,毕竟像JVM这样以插入一层虚拟机并牺牲一定的性能为代价,召唤出“上帝视角”后,什么事都能干成,因为一切的一切都被一个上帝管着,区区未指向的内存块的回收就不在话下了,你找不到它,上帝找得到。“BB is watching you!”(老大哥在看着你!)

2, 通用固定大小内存池

上述内存池的实现嵌入在Complex代码中,只具备最低级的代码重用性(即复制粘贴方式的重用)。下面这个内存池通过模板的方式,适用于所有的单一类型。

#include <stdio.h>
#include <time.h> class MemPoolNext
{
public: MemPoolNext * next;
}; template <class T>
class MemPool
{
enum {EXPAND_SIZE = };
static MemPoolNext * freelist;
static void expand()
{
size_t size = sizeof(T);
if(size < sizeof(void *))
size = sizeof(void *);
MemPoolNext * p = (MemPoolNext *) new char [size];
freelist = p;
for(int i=; i<EXPAND_SIZE; i++, p=p->next)
p->next = (MemPoolNext *) new char [size];
p->next = ;
}
public:
static void * alloc (size_t)
{
if( == freelist)
expand();
void * p = freelist;
freelist = freelist->next;
return p;
}
static void free (void * p, size_t)
{
((MemPoolNext *) p)->next = freelist;
freelist = (MemPoolNext *) p;
}
static void deletepool()
{
MemPoolNext * p = freelist;
while( p!= )
{
freelist = p->next;
delete p;
p = freelist;
}
}
};
template <class T>
MemPoolNext * MemPool<T>::freelist = ; class Complex
{
public:
double re;
double im;
explicit Complex(double r = ., double i = .): re(r), im(i){}
inline static void * operator new (size_t s)
{
return MemPool<Complex>::alloc(s);
}
inline static void operator delete (void * p, size_t s)
{
MemPool<Complex>::free(p, s);
}
inline static void deletepool()
{
MemPool<Complex>::deletepool();
}
}; main()
{
double from = time();
Complex * c[];
for (int i=; i<; i++)
{
for(int j=; j<; j++)
c[j] = new Complex(j, j);
for(int j=; j<; j++)
delete c[j];
}
Complex::deletepool();
double to = time();
printf("%f\n", to-from);
}

实际上很简单,无非是将原来写在Complex中用来管理内存的代码封装成了一个新的类MemPool。

三,多线程固定大小内存池

代码略,只需在临界区前后加上锁即可,最常用的锁是pthread_mutex_lock(信号量锁)。

四,多线程非固定大小内存池

此时,一个内存池内的对象已不局限于单一的类,而是能够同时包容不同类型的对象。代码略,只需在开辟和回收内存时,考虑当前可用的内存大小和已分配的内存大小即可。

C++实现简单的内存池的更多相关文章

  1. 简单内存池的C实现

    1. 序言 对于程序开发人员来说,会经常听到这种"池"的概念,例如"进程池","线程池","内存池"等,虽然很多时没有吃 ...

  2. 重写boost内存池

    最近在写游戏服务器网络模块的时候,需要用到内存池.大量玩家通过tcp连接到服务器,通过大量的消息包与服务器进行交互.因此要给每个tcp分配收发两块缓冲区.那么这缓冲区多大呢?通常游戏操作的消息包都很小 ...

  3. 一个简易内存池(C++)

    做这个内存池主要是为了完成一道面试题,题目在代码中. 代码 #include <iostream> #include<string> #include <list> ...

  4. C语言内存管理(内存池)

    C语言可以使用alloc从栈上动态分配内存. 内存碎片 Malloc/free或者new/delete大量使用会造成内存碎片,这种碎片形成的机理如下: 内存碎片一般是由于空闲的内存空间比要连续申请的空 ...

  5. 基于C/S架构的3D对战网络游戏C++框架 _05搭建系统开发环境与Boost智能指针、内存池初步了解

    本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): ...

  6. 【uTenux实验】内存池管理(固定内存池和可变内存池)

    1.固定内存池管理实验 内存管理是操作系统的一个基础功能.uTenux的内存池管理函数提供了基于软件的内存池管理和内存块分配管理.uTenux的内存池有固定大小的内存池和大小可变的内存池之分,它们被看 ...

  7. 对象池与.net—从一个内存池实现说起

    本来想写篇关于System.Collections.Immutable中提供的ImmutableList里一些实现细节来着,结果一时想不起来源码在哪里--为什么会变成这样呢--第一次有了想写分析的源码 ...

  8. nginx源码学习----内存池

    最近在进行监控平台的设计,之前一直觉得C/C++中最棘手的部分是内存的管理上,远不止new/delete.malloc/free这么简单.随着代码量的递增,程序结构复杂度的提高.各种内存方面的问题悄然 ...

  9. nginx源码分析—内存池结构ngx_pool_t及内存管理

    Content 0. 序 1. 内存池结构 1.1 ngx_pool_t结构 1.2 其他相关结构 1.3 ngx_pool_t的逻辑结构 2. 内存池操作 2.1 创建内存池 2.2 销毁内存池 2 ...

随机推荐

  1. JS验证框架(exValidation)

    exValidation是一个前台校验框架 能够校验前台的常用的输入错误. 例如,必须输入,用户输入长度...... ----------------------------------------- ...

  2. asp.net运算符之逻辑运算符以及其他运算符

    逻辑(布尔型)运算符用于对boolean型的结果的表达式进行运算,运算的结果都是boolean型.其运算结果如下所示: 运算符 运算 例子 结果 & AND(与) false&true ...

  3. C# 中怎么将string转换成int型

    int intA = 0;1.intA =int.Parse(str);2.int.TryParse(str, out intA);3.intA = Convert.ToInt32(str);以上都可 ...

  4. JqueryPagination 分页插件使用说明

    JqueryPagination是个简单轻量级的分页插件,使用起来很容易,只要初始化一个实例,并设置总数量.翻页回调函数.其它参数就可以实现无刷新分页功能了 1 首先引入相关js文件: <lin ...

  5. 【学习笔记】【C语言】函数

    一. 什么是函数 任何一个C语言程序都是由一个或者多个程序段(小程序)构成的,每个程序段都有自己的功能,我们一般称这些程序段为“函数”.所以,你可以说C语言程序是由函数构成的. 比如你用C语言编写了一 ...

  6. UI3_UIViewController生命周期

    // // SecondViewController.h // UI3_UIViewController生命周期 // // Created by zhangxueming on 15/7/2. // ...

  7. MYSQL基础01(新增,修改,删除)

    首先说明一下,本人之前一直都是用MSSQL,由于工作原因,每天写上百条sql语句,并持续了几年;由于换了工作目前主要接触的MYSQL;所以现在开始学习MYSQL. 我的学习计划很简单,我在MSSQL使 ...

  8. jquery.cookie.js 使用方法

    Cookies 定义:让网站服务器把少量数据储存到客户端的硬盘或内存,从客户端的硬盘读取数据的一种技术: 下载与引入:jquery.cookie.js基于jquery:先引入jquery,再引入:jq ...

  9. frame和iframe区别

    1.frame不能脱离frameSet单独使用,iframe可以: 2.frame不能放在body中:如下可以正常显示: <!--<body>--> <frameset ...

  10. HyperMesh生成Flac3D的剖分网格

    本帖的目的是探索煤矿沉积岩层采煤过程中的力学分析模拟的前处理方法,计算软件采用公认的Flac3D差分方法. 目前,Flac3D官方提供的剖分网格的生成方法有三种.一是直接使用命令和Fish语句生成,这 ...