C++实现简单的内存池
多进程编程多用在并发服务器的编写上,当收到一个请求时,服务器新建一个进程处理请求,同时继续监听。为了提高响应速度,服务器采用进程池的方法,在初始化阶段创建一个进程池,池中有许多预创建的进程,当请求到达时,只需从池中分配出来一个进程即可;当进程不够用时,进程池将再次创建一批进程。类似的方法可以用在内存分配上。
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++实现简单的内存池的更多相关文章
- 简单内存池的C实现
1. 序言 对于程序开发人员来说,会经常听到这种"池"的概念,例如"进程池","线程池","内存池"等,虽然很多时没有吃 ...
- 重写boost内存池
最近在写游戏服务器网络模块的时候,需要用到内存池.大量玩家通过tcp连接到服务器,通过大量的消息包与服务器进行交互.因此要给每个tcp分配收发两块缓冲区.那么这缓冲区多大呢?通常游戏操作的消息包都很小 ...
- 一个简易内存池(C++)
做这个内存池主要是为了完成一道面试题,题目在代码中. 代码 #include <iostream> #include<string> #include <list> ...
- C语言内存管理(内存池)
C语言可以使用alloc从栈上动态分配内存. 内存碎片 Malloc/free或者new/delete大量使用会造成内存碎片,这种碎片形成的机理如下: 内存碎片一般是由于空闲的内存空间比要连续申请的空 ...
- 基于C/S架构的3D对战网络游戏C++框架_05搭建系统开发环境与Boost智能指针、内存池初步了解
本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): ...
- 【uTenux实验】内存池管理(固定内存池和可变内存池)
1.固定内存池管理实验 内存管理是操作系统的一个基础功能.uTenux的内存池管理函数提供了基于软件的内存池管理和内存块分配管理.uTenux的内存池有固定大小的内存池和大小可变的内存池之分,它们被看 ...
- 对象池与.net—从一个内存池实现说起
本来想写篇关于System.Collections.Immutable中提供的ImmutableList里一些实现细节来着,结果一时想不起来源码在哪里--为什么会变成这样呢--第一次有了想写分析的源码 ...
- nginx源码学习----内存池
最近在进行监控平台的设计,之前一直觉得C/C++中最棘手的部分是内存的管理上,远不止new/delete.malloc/free这么简单.随着代码量的递增,程序结构复杂度的提高.各种内存方面的问题悄然 ...
- 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 ...
随机推荐
- jquery设置元素readonly和disabled(checkbox只读)
jquery api中提供了对元素应用disabled和readonly属性的方法:1,readonly 代码示例: $('input').attr("readonly",&quo ...
- cocos2d-x中Node中重要的属性
Node还有两个非常重要的属性:position和anchorPoint. position(位置)属性是Node对象的实际位置.position属性往往还要配合使用anchorPoint属性,为了将 ...
- 前台JS(Jquery)调用后台方法 无刷新级联菜单示例
前台用AJAX直接调用后台方法,老有人发帖提问,没事做个示例 下面是做的一个前台用JQUERY,AJAX调用后台方法做的无刷新级联菜单 http://www.dtan.so CasMenu.aspx页 ...
- python简单爬虫编写
1.主要学习这程序的编写思路 a.读取解释网站 b.找到相关页 c.找到图片链接的元素 d.保存图片到文件夹 ..... 将每一个步骤都分解出来,然后用函数去实现,代码易读性高. ##代码尽快运行时会 ...
- 正确的安装和使用nvm
前言 目前主流的node版本管理工具有两种,nvm和n.两者差异挺大的,具体分析可以参考一下淘宝FED团队的一篇文章: 管理 node 版本,选择 nvm 还是 n? 总的来说,nvm有点类似于 Py ...
- const type& 与 type& 的区别
const type& 与 type& 是C/C++编程中容易混淆的两个知识点,现在以 cont int& 与 int& 为例讲解: 1.int& 讲解 int ...
- CustomMessageBox使用总结
开发过程中难免要使用到消息框,然而系统提供的MessageBox却难以满足许多需求.一.MessageBox的背景颜色无法更改,这就无法满足需求要求的消息框颜色.二.MessageBox的提示形式过于 ...
- NDK jni 加载静态库
加载静态库到android,静态库的提供方式有2种, a. 通过源文件来编译静态库 b. 加载已经编译好的静态库 首先我们来看,通过源文件来编译静态库,工程目录如下 第一步:我们来看我们的jni目录, ...
- ADO.NET笔记——SQL注入攻击
相关知识: 可以通过字符串的拼接来构造一个SQL命令字符串,但是SQL命令字符串的拼接确是造成“SQL注入攻击”的重要原因. 考虑下列例子:从ProductCategory表中检索出Name为“Bik ...
- 主机win10与虚拟机ubuntu14.04通信
主机是笔记本win10系统,在virtualbox虚拟机里面安装了ubuntu14.04系统,现在想让它们互联互通. 我的笔记本是通过路由器无线连接接入的互联网,设置了固定ip:192.168.0.4 ...