65.9K
CodeProject 正在变化。 阅读更多。
Home

使用模板和 std::vector 的简单内存池

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.27/5 (6投票s)

2012年6月19日

CPOL

3分钟阅读

viewsIcon

21512

使用模板和向量创建简单的内存池。

引言

一种使用模板和 vector 实现内存池来避免内存碎片的方法。

背景  

在运行 24/7/365 处理来自不同客户端(具有不同需求)的“请求”的服务器应用程序时,我们遇到了一个问题:应用程序随着时间的推移变得越来越慢。 要求用户不时重启服务器是不可取的。

经过数天的分析和调试,以及(是的)祈祷希望找到问题的征兆,我将其缩小到 'new' 和 'delete' - 碎片化。 我们处理数量不固定的、大小不同的请求,并且具有“接收(创建)-处理(销毁)”的生命周期。

问题在于请求数据的大小。 让我详细说明一下。

由于请求的大小不同,小的内存分配穿插在中等和大的分配之间。 服务器是多线程的,因此处理这些请求需要不同的时间,并导致以随机顺序释放块。 我们无法确保内存以最佳顺序释放(最佳顺序与分配顺序完全相反)。

最终结果是存在大量小块,当出现中等或大型分配请求时,必须跳过或合并这些块,这反过来又导致请求新分配时出现延迟增加。

我面临两种可能的方法:全局或按请求(类)基础。

全局 - 重载全局运算符 new 和 delete,编写一个所有类和函数都将使用的新内存管理器,确保我的内存管理器首先初始化(在 C++ 运行时系统发出任何内存请求之前,但在它创建应用程序堆之后)... 太多 敏感的 工作 容易出错。 (没有人能说我不知道广泛的项目影响...)

因此,决定是按请求基础来解决它(真是个惊喜!!!)

解决方案

我决定为任何给定时间可能出现的最多请求数量预分配内存(在我的情况下 - 每秒不超过 2000 个)。

由于我想编写一个类来处理所有情况,但仍然是按请求基础的,所以模板是一个不错的选择。 它需要在需要时分配内存,并在不需要时重用同一内存。 使用 vector 来模拟堆栈。

第一个版本是这样的:

// MemoryPool class
//
template<class T>
class _MemoryPool
{
public:
    _MemoryPool( int nSize ) : m_Items( new T [ nSize ] )
    {
        m_FreeItems.reserve( nSize );
        for( int i = 0; i < nSize; ++i )
        {
            m_FreeItems.push_back( &m_Items[i] );
        }
    }

    ~_MemoryPool(void)
    {
        delete [] m_Items;
    }

    T * Allocate()
    {
        T * pItem = m_FreeItems.back();
        m_FreeItems.pop_back();
        return( pItem );
    }

    void Free( T * pItem )
    {
        m_FreeItems.push_back( pItem );
    }

private:
    std::vector< T* > m_FreeItems;
    T * m_Items;
};

在我们的内存池类的构造函数中,将创建所需数量的对象,并加载到 std::vector 中。 新的分配请求将从我们的 vector 中取出顶部对象(在此过程中将其从 vector 中删除)。 当不再需要对象时,它将被放回我们的 vector 的顶部。 因此,这里的 vector 模拟了具有 push-pop 功能的堆栈。

// Request/data class
// Example
class RequestData1
{
public:
    int nReqType;
    double dReqTimeReceived;
    unsigned long nActionBits;
};
// next request class...

请求是数据结构...

现在,使用 xxx 个对象创建内存池。

// construct our memory pool
// max 2000 requests
_MemoryPool< RequestData1 > g_poolRequest1(2000);
// next memory pool for next request class

到目前为止,一切都好,现在我需要做的就是写:

RequestData1* req1 = g_poolRequest1.Allocate();
// Call placement new on our raw data.
new (req1) RequestData1( call, constructor, with, some, Parameters, if, needed);

// we process req1... ... not need anymore after this
g_poolRequest1.Free( req1 );

好吧,我不想去我创建请求的每个代码片段,并将其更改为这种结构(并且也释放位)。 解决方案:为每个请求类添加运算符 new 和 delete。 简单。

现在请求类是这样的:

class RequestData1
{
public:
    int nReqType;
    double dReqTimeReceived;
    unsigned long nActionBits;

    void * operator new( size_t n )
    {
        return( g_poolRequest1.Allocate() );
    };

    void operator delete( void * p )
    {
        g_poolRequest1.Free( reinterpret_cast<RequestData1 *>(p) );
    };
};

而且,现在剩下的就是重新编译...(并祈祷 2001 个请求永远不会到来...)

目前就这些,未包含测试应用程序,顺便说一句,应用程序甚至使用这种方法提高了速度,但这并不是本次练习的最终目标。

仍然存在更多碎片问题(指向字符串),但解决方案将在未来的文章中讨论,也许。

保重...

© . All rights reserved.