一个非常简单的C++内存池方案

在游戏中频繁使用new与delete将会导致性能的下降,还可能造成内存碎片。

使用以一个自定义的内存分配器将是很重要的。

创建一个通用又强大效率性能又高的内存分配器将是困难的,所以这个的分配器面向下面的情况使用:

1. 一开始就就申请,直到游戏退出才释放

这种情况我们无需做分配器内部的内存释放,仅需将分配器本身释放掉即可

2. 申请后立即释放

当然不算是立即释放,不然就没用了。

这种情况一般是需要一个临时缓冲区,或者使用一个临时对象。

我们的内存可以记录上次申请的情况,方便重复利用内存资源。

当然,我们还需要支持一个强大的功能——任意字节对齐

内存对齐对提高性能很有帮助,特别地,有些地方对字节对齐有硬性要求。

比如SIMD(单指令流多数据流)操作矩阵或者矢量时,就要求128位即16字节对齐。

我们就仅需将这些数据排到前面即可。

特别地,如果使用继承特别是继承抽象类需要特别注意数据在内存的排列方法。

比如继承一个抽象类,那么在数据的前4字节(32位程序)将会是虚表指针,需要16字节对齐的话,

需要在前面写一些有用没用数据,比如指针啥的。

好了,放代码,不足百行

// 只申内存池
// EachPoolSize : 每片缓冲池的大小 略大于实际能分配内存数
template<size_t EachPoolSize=128 * 1024> class AllocOnlyMPool{
    // 节点
    struct Node{
        // 后节点
        Node*               next = nullptr;
        // 已分配数量
        size_t              allocated = 0;
        // 上次分配位置
        BYTE*               last_allocated = nullptr;
        // 缓冲区
        BYTE                buffer[0];
    };
public:
    // 构造函数
    AllocOnlyMPool(){
        assert(m_pFirstNode && "<AllocOnlyMPool::AllocOnlyMPool>:: null m_pFirstNode");
    }
    // 析构函数
    ~AllocOnlyMPool();
    // 申请内存
    void*               Alloc(size_t size, UINT32 align = sizeof(size_t));
    // 释放内存
    void                Free(void* address);
private:
    // 申请节点
    static __forceinline Node* new_Node(){
        auto* pointer = reinterpret_cast<Node*>(malloc(EachPoolSize));
        pointer->Node::Node();
        return pointer;
    }
    // 首节点
    Node*       m_pFirstNode = new_Node();
};

// **实现**

// 申请内存
template<size_t EachPoolSize>
void* AllocOnlyMPool<EachPoolSize>::Alloc(size_t size, UINT32 align){
    if (size > (EachPoolSize - sizeof(Node))){
#ifdef _DEBUG
        assert(!"<AllocOnlyMPool<EachPoolSize>::Alloc>:: Alloc too big");
#endif
        return nullptr;
    }
    // 获取空闲位置
    auto* now_pos = m_pFirstNode->buffer + m_pFirstNode->allocated;
    // 获取对齐后的位置
    auto aligned = (reinterpret_cast<size_t>(now_pos)& (align - 1));
    if (aligned) aligned = align - aligned;
    now_pos += aligned;
    // 增加计数
    m_pFirstNode->allocated += size + aligned;
    // 检查是否溢出
    if (m_pFirstNode->allocated > (EachPoolSize - sizeof(Node))){
        Node* node = new_Node();
        if (!node) return nullptr;
        node->next = m_pFirstNode;
        m_pFirstNode = node;
        // 递归(仅一次)
        return Alloc(size, align);
    }
    // 记录上次释放位置
    m_pFirstNode->last_allocated = now_pos;
    return now_pos;
}

// 释放内存
template<size_t EachPoolSize>
void AllocOnlyMPool<EachPoolSize>::Free(void* address){
    // 上次申请的就这样了
    if (address && m_pFirstNode->last_allocated == address){
        m_pFirstNode->allocated =
            (m_pFirstNode->last_allocated - m_pFirstNode->buffer);
        m_pFirstNode->last_allocated = nullptr;
    }
}

// AllocOnlyMPool 析构函数
template<size_t EachPoolSize>
AllocOnlyMPool<EachPoolSize>::~AllocOnlyMPool(){
    Node* pNode = m_pFirstNode;
    Node* pNextNode = nullptr;
    // 顺序释放
    while (pNode)
    {
        pNextNode = pNode->next;
        free(pNode);
        pNode = pNextNode;
    }
}

顺序说明一下:

1. 使用模板,参数是每个单元的大小,因为储存了数据,实际每个单元能够分配的内存量略小,

看实际情况赋予参数吧,128k一般够了。大不了上兆,反正内存白菜价了。

模板还可能增加目标程序大小,可改为变量。但是一般用一个模板示例就行了,就无所谓了

2.Node使用空数组(中括号里面啥也没有),标准C++支不支持我不知道,反正VC++警告了”非标准扩展“,

不过空数组是在C99里面允许的

3.对指针行进位操作进行对齐操作,虽说是任意字节对齐。

但是一般得低于4k,因为一般操作系统一页的大小为4k,高于4k将毫无意义。

使用方法: 实例化对象。  需要全局使用的话就用静态变量。

多线程支持。

游戏一般将会是多线程的,所以应该对多线程进行支持。

但是上锁与解锁需要时间,违背的这个内存池的设计初衷。

所以一般游戏推荐的是“无锁操作”,所以在这就不对其进行上锁。

那怎么保证安全呢?

我们仅需保证对其进行互斥操作——为每个需要这个内存池的线程分配一个内存池即可

这就是对象内存池的好处

时间: 2024-11-03 13:35:57

一个非常简单的C++内存池方案的相关文章

【源码剖析】MemoryPool —— 简单高效的内存池 allocator 实现

什么是内存池?什么是 C++ 的 allocator? 内存池简单说,是为了减少频繁使用 malloc/free new/delete 等系统调用而造成的性能损耗而设计的.当我们的程序需要频繁地申请和释放内存时,频繁地使用内存管理的系统调用可能会造成性能的瓶颈,嗯,是可能,毕竟操作系统的设计也不是盖的(麻麻说把话说太满会被打脸的(⊙v⊙)).内存池的思想是申请较大的一块内存(不够时继续申请),之后把内存管理放在应用层执行,减少系统调用的开销. 那么,allocator 呢?它默默的工作在 C++

一个及其简化版本的内存池实现

最近写的一个程序中需要频繁的申请和释放零碎的内存块,这些内存块的大小却只有简单的几种.如果直接使用系统调用malloc/free.new/delete进行内存分配和释放,则效率很低.程序运行时间长了会产生大量的内存碎片.想起uC/OS-II 里面提供了个内存分配单元,正好满足我的需要.就把里面相关的代码扒了出来.写成了一个内存池的类. 这个内存池的功能非常的简单,初始化时分配一大块内存,然后将各个小内存块串成一个单项链表.每次分配内存块时从链表的头上去取一个内存块.回收内存块时也是将内存块插到链

boost内存池的使用介绍

Boost库的pool提供了一个内存池分配器,用于管理在一个独立的.大的分配空间里的动态内存分配. Boost库的pool主要适用于快速分配同样大小的内存块,尤其是反复分配和释放同样大小的内存块的情况.使用pool内存池主要有以下两个优点: 1. 能够有效地管理许多小型对象的分配和释放工作,避免了自己去管理内存而产生的内存碎片和效率低下问题. 2.  告别程序内存泄漏的烦恼,pool库会在内部对内存自动进行管理,避免了程序员一不小心而造成的内存泄漏问题. pool库主要提供了四种内存池接口,分别

Nginx系列三 内存池的设计

Nginx的高性能的是用非常多细节来保证,epoll下的多路io异步通知.阶段细分化的异步事件驱动,那么在内存管理这一块也是用了非常大心血.上一篇我们讲到了slab分配器,我们能够能够看到那是对共享内存的管理的优化.Nginx在进程内也实现了自己的内存池,目的在于降低内存碎片,降低向操作系统的申请次数,减低模块开发难度.Nginx实现的内存池实际上非常easy: Nginx内存池的管理是分大内存和小内存的,详细说来.Nginx在向系统初始化内存池后,当我们调用ngx_palloc申请小内存后,再

Linux简易APR内存池学习笔记(带源码和实例)

先给个内存池的实现代码,里面带有个应用小例子和画的流程图,方便了解运行原理,代码 GCC 编译可用.可以自己上网下APR源码,参考代码下载链接: http://pan.baidu.com/s/1hq6A20G 贴两个之前学习的时候参考的文章地址,大家可以参考: http://www.cnblogs.com/bangerlee/archive/2011/09/01/2161437.html http://blog.csdn.net/flyingfalcon/article/details/2627

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

做这个内存池主要是为了完成一道面试题,题目在代码中. 代码 1 #include <iostream> 2 #include<string> 3 #include <list> 4 using namespace std; 5 6 //一个简单的内存池,池中保存3块内存分别为1k,2k,4k 7 //实现池子的malloc(size)和free(void*)操作 8 //不考虑跨块申请内存情况 9 10 class node 11 { 12 public: 13 int

一个依靠STL vector的接口进行申请和回收管理的内存池类( c++ 封装)

其他出现两次,只有一个出现一次的那道题我就不更了,直接抑或,最后的结果就是那个数.为什么可以这样做呢?因为一个32位int,如果所有数都出现了两次,那么为1的那些位统计的个数一定是2的倍数,抑或之后全变成0.一个数出现了一次,它为1的那些位上,1的个数必定是奇数,抑或之后一定还是1. 我之前知道出现两次这个题的解法,但是理解的不够深,以为抑或是关键,其实不是,出现了偶数次才是关键.理解了这点,推广到出现3次上,如果所有的出现了三次,那么为1的那些位1的个数一定是三的倍数,那如果有一个数出现了一次

windows平台上的一个内存池的实现

.h文件 /**********************说明************************* * 这是MPool内存池的实现,他具有如下特性: * 1. 池中的内存块是大小是相同的 * 2. 由宏定义_MP_NO_SERIALIZE决定是否需要多线程同步 * 3. 他利用windows的堆内存API进行内存分配 * 4. 他不能替换crt的malloc和free * 5. 他不是一个通用型的内存池 * 6. 适用于特定的应用环境(高频率的申请释放内存,如网络服务器),应用环境影

简单的内存池实现gko_alloc

在用gpreftools优化gko_pool的时候我发现一个问题,malloc竟然成了性能瓶颈 由于在每个连接建立的时候gko_pool默认会为读写各分配2KB的buf备用,这个是比较固定的 每个连接的的生命周期会伴随着4KB大小的内存malloc & free 正好可以写个只能分配固定大小内存的"内存池",基本思路就是每次分配一个大内存bucket(64MB),需要4KB的块的时候就从bucket中取,当bucket没有可用slot就再分配一个新的bucket,当bucket