MFC基于CPlex结构的内存池化管理

 

CMapPtrToPtr的内存管理问题

CMapPtrToPtr类保存的是若干个映射项的集合。每个映射项保存了一对映射关系,一个称为key),相当于数学中的 x,另一个称为value),相当于y。为了将这些映射关系连在一起,还要在每个映射项中记录下下一个映射项的地址,所以可以用下面的 CAssoc 结构表示一对映射关系。

// AFXCOLL.H

class CMapPtrToPtr : public CObject

{

protected:

    // Association

    struct CAssoc

    {

       CAssoc* pNext;

       void* key;

       void* value;

    };

 

protected:

    int m_nCount; // 记录了程序一共使用了多少个CAssoc结构,即关联的个数

    CAssoc* m_pFreeList;

    struct CPlex* m_pBlocks;

    int m_nBlockSize;

 

    CAssoc* NewAssoc();

    void FreeAssoc(CAssoc*);

};

此结构表示给定一个 key,仅有一个 value 与它相对应。如果有多组这样一一对应的数据,就要在内存中分配多个具有 CAssoc结构大小的空间来保存各成员的值。其中pNext 成员将这些内存块连在一起

按照这种链表的结构,假设用户要用 CMapPtrToPtr 类保存成千上万条中文和英文的对应数据,就要在内存中 new上万个 CAssoc 结构,调用这上万个new函数的开销是多大?为了能够正确的销毁,new 函数又要向每个 CAssoc 结构中添加额外的信息(delete靠这些记录信息才能正确的释放内存),这又会浪费多少内存?

如此多大小相同的内存块不断地被分配、释放产生的结果是什么呢?内存碎片

如果两个 CAssoc 占用的是不连续的内存空间,而且中间间隔的空间又恰好不足以容纳另一个 CAssoc结构,就会有内存碎片产生。通常解决这个问题的比较好的方法是预先 CAssoc结构申请一块比较大的内存,当要为 CAssoc 结构分配空间的时候,并不真的申请新的空间,而是让它使用上面预留的空间,直到这块空间被使用完申请新的空间。此即内存的池化管理(Memory Pool)。

写一个分配内存的全局函数,每一次调用此函数都可以获得一个指定大小的内存块来容纳多个 CAssoc 结构。另外,还必须要有一种机制将此函数申请的内存块记录下来,以便当CMapPtrToPtr类的对象销毁的时候释放所有内存空间。在每个内存块头部安排指向下一个内存块首地址的 pNext 指针就可以将所有内存块链接在一起了,这样做的结果是每一个内存块都由 pNext 指针和真正的用户数据组成,如下图所示。只需要记录下 pHead 指针就有办法释放所有内存空间了。

内存的组织形式(内存链)

在每一块内存的头部增加的数据可以用CPlex结构来表示。当然,此结构只有一个pNext成员,但是为了方便,把分配内存的全局函数以静态函数的形式封装到CPlex结构中,把释放内存链的函数也封装到其中。

 

CPlex结构

CPlex结构是真正进行CRT动态内存分配操作的执行者,它仅包含一个指针,指向另一个CPlex对象以创建一个CPlex链表,所以它的大小只为4字节。当进行内存分配时,就需要sizeof(CPlex) + nMax * cbElement

// AFXPLEX_.H

struct CPlex     // warning variable length structure

{

    CPlex* pNext; // chaining pointer

#if (_AFX_PACKING >= 8)

    DWORD dwReserved[1];    // align on 8 byte boundary

#endif

    // BYTE data[maxNum*elementSize];

 

    void* data() { return this+1; } // pay attention to this+1,skip all members of CPlex,the offset is sizeof(CPlex))

 

    static CPlex* PASCAL Create(CPlex*& head, UINT nMax, UINT cbElement);

           // like 'calloc' but no zero fill

           // may throw memory exceptions

 

    void FreeDataChain();       // free this one and links

};

CPlex::Create是最终分配内存的全局函数,这个函数会将所分配的内存添加到以pHead为首地址的内存链中。参数pHead是用户提供的保存链中第一个内存块的首地址的指针。以后释放此链的内存时,直接使用“pHead->FreeDataChain()”语句即可。下面是这些函数的具体实现,应放在 PLEX.CPP 文件中。

// PLEX.CPP

CPlex* PASCAL CPlex::Create(CPlex*& pHead, UINT nMax, UINT cbElement)

{

    ASSERT(nMax > 0 && cbElement > 0);

 

    CPlex* p = (CPlex*) new BYTE[sizeof(CPlex) + nMax * cbElement];

           // may throw exception

 

    p->pNext = pHead;

    pHead = p;  // change head (adds in reverse order for simplicity)

 

    return p;

}

 

void CPlex::FreeDataChain()     // free this one and links

{

    CPlex* p = this;

 

    while (p != NULL)

    {

       BYTE* bytes = (BYTE*) p; // every object is a block of bytes

       CPlex* pNext = p->pNext;

       delete[] bytes;

       p = pNext;

    }

}

这种管理内存的方式很简单,也很实用。使用的时候除了调用CPlex::Create函数为小的结构申请大的内存空间以外,还要定义一个CPlex类型的指针用于记录整个链的首地址。下面的示例程序先用CPlex::Create函数申请了一大块内存,在使用完毕以后又通过 CPlex 指针将之释放,代码如下。

// CPlex使用例程

#include "afxplex_.h" // 包含定义CPlex结构的文件

struct CMyData

{

    int nSomeData;

    int nSomeMoreData;

};

 

int main()

{

    CPlex* pBlocks = NULL; // 用于保存链中第一个内存块的首地址,必须被初始化为NULL

    CPlex::Create(pBlocks, 10, sizeof(CMyData));

    CMyData* pData = (CMyData*)pBlocks->data();

    // 现在pDataCPlex::Create函数申请的10CMyData结构的首地址

    //...    // 使用pData指向的内存

    // 使用完毕,继续申请

    CPlex::Create(pBlocks, 10, sizeof(CMyData));

    pData = (CMyData*)pBlocks->data();

    // 最后释放链中的所有内存块

    pBlocks->FreeDataChain();

 

return 0;

}

CPlex::Create 函数是 CPlex 的静态成员,使用起来就相当于全局函数,所以上面的代码直接调用CPlex::Create 函数来为 CMyData 结构申请一块大的空间,空间的首地址返回给pBlocks 变量。

最后的一条语句会释放掉前面CPlex::Create申请的全部空间。

 

CMapPtrToPtr的内存管理方案

由于 CAssoc 结构只会被 CMapPtrToPtr 类使用,所以把它的定义放在了类中。NewAssoc函数负责在预留的空间中给 CAssoc结构分配空间,如果预留的空间已经使用完了,它会调用CPlex::Create 函数申请 m_nBlockSize*sizeof(CAssoc)大小的内存块,代码如下。

CMapPtrToPtr::CAssoc* CMapPtrToPtr::NewAssoc()

{

    if (m_pFreeList == NULL)

    {

       // add another block

       CPlex* newBlock = CPlex::Create(m_pBlocks, m_nBlockSize, sizeof(CMapPtrToPtr::CAssoc));

       // chain them into free list

       CMapPtrToPtr::CAssoc* pAssoc = (CMapPtrToPtr::CAssoc*) newBlock->data();

       // free in reverse order to make it easier to debug

       pAssoc += m_nBlockSize - 1;

       for (int i = m_nBlockSize-1; i >= 0; i--, pAssoc--)

       {

           pAssoc->pNext = m_pFreeList;

           m_pFreeList = pAssoc;

       }

    }

    ASSERT(m_pFreeList != NULL);  // we must have something

 

    CMapPtrToPtr::CAssoc* pAssoc = m_pFreeList;

    m_pFreeList = m_pFreeList->pNext;

    m_nCount++;

    ASSERT(m_nCount > 0);  // make sure we don't overflow

 

    pAssoc->key = 0;

    pAssoc->value = 0;

 

    return pAssoc;

}

 

void CMapPtrToPtr::FreeAssoc(CMapPtrToPtr::CAssoc* pAssoc)

{

// just recycle to free list,not really free

    pAssoc->pNext = m_pFreeList;

    m_pFreeList = pAssoc;

    m_nCount--;

    ASSERT(m_nCount >= 0);  // make sure we don't underflow

 

    // if no more elements, cleanup completely

    if (m_nCount == 0)

       RemoveAll();

}

CMapPtrToPtr没有被使用的CAssoc结构连成了一个空闲链,成员 m_pFreeList 是这个链的头指针。

这是 CAssoc结构中 pNext 成员的一个重要应用。为 CAssoc 结构分配新的内存(NewAssoc)或释放 CAssoc 结构占用的内存(FreeAssoc)都是围绕 m_pFreeList 指向的空闲链进行的。类的构造函数会初始化 m_pFreeList 的值为 NULL,所以在第一次调用 NewAssoc 函数的时候程序会申请新的内存块,此内存块的头部是一个 CPlex 结构,紧接着是可以容纳 m_nBlockSize CAssoc 结构的空间,m_pBlocks 成员保存的是内存块链的头指针,以后还要通过该成员调用 FreeDataChain 函数释放所有的内存块。申请新的内存块以后就要向空闲链中添加元素了。真正从空闲链中移去元素的过程是很简单的,同 FreeAssoc函数向空闲链中添加元素的过程非常相似,都是要改变头指针 m_pFreeList

CMap*系列和C*List系列均采用基于CPlex结构的内存池化管理。

 

CFixedAlloc类简介

基于CPlex结构的CFixedAlloc类(FIXALLOC.H/ FIXALLOC.CPP)保存的是若干节点CNode的集合。这里CNode只有一个成员CNode* pNextpNext指向下一个节点,以便链化管理。实际应用中,CNode会有实际的数据成员,因此其构造函数中ASSERT(nAllocSize >= sizeof(CNode));

CFixedAlloc是一个非常简单的类,它由临界区m_protect提供线程安全。m_nAllocSize中包含了类的对象大小,m_nBlockSize指定了每个固定内存块能包含的对象数,这两个成员都在构造函数中设置。

在类声明中添加DECLARE_FIXED_ALLOC()宏,在含有类定义的CPP文件中添加IMPLEMENT_FIXED_ALLOC()宏,这样该类将调用CFixedAlloc类(重载operator newoperator delete)对内存进行优化管理。

 

参考:

Windows程序设计》王艳平

用未公开的MFC类加强动态内存分配

池內春秋-Memory Pool 設計哲學和無痛運用

Apache内存池内幕

 

碎片式内存池的可行性分析

C++ Memory Pool

一种自适应变长块内存池

基于C语言的内存池的设计与实现

young library 的轻量级内存池设计与实现

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 基于matlab的yalmip cplex是一种优工具,可以用于解决各种数学优问题。它结合了matlab的强大计算能力和cplex的高效求解能力,可以快速地求解线性规划、整数规划、二次规划等问题。同时,yalmip还提供了一种方便的建模语言,使得用户可以更加方便地描述优问题。 ### 回答2: YALMIP是一个MATLAB工具箱,用于建立数学模型,以及各种优问题的建模和解决,包括线性规划,非线性规划,半定规划等。而CPLEX是IBM公司的一个商业优软件工具,可用于解决复杂的线性规划、整数规划和混合整数规划等问题。 将YALMIP与CPLEX结合使用可以得到更为强大的优求解能力,因为YALMIP可以方便地创建优问题的约束和目标函数,而CPLEX则可以提供高效且准确的求解方法。对于过于复杂的问题,使用YALMIP结合CPLEX可以得到更好的数值解,尽管可能会稍有时间成本。 在使用YALMIP建立数学模型时,我们只需简单地定义模型的约束和目标函数,然后指定优求解方法为CPLEX即可。例如,可以使用以下代码建立一个简单的线性规划模型: ```matlab x = sdpvar(n,1); % 定义优变量 A = randn(m,n); b = randn(m,1); % 定义约束矩阵 objective = sum(x); % 定义目标函数 constraints = [A*x <= b, x >= 0]; % 定义约束条件 ops = sdpsettings('solver','cplex'); % 指定优求解器为CPLEX optimize(constraints,objective,ops); % 求解 ``` 通过使用YALMIP和CPLEX的结合,可以大大简求解复杂优模型的过程,并且可以使用MATLAB的其他强大功能来进行可视和分析。但是需要注意的是,CPLEX是商业软件,需要购买和安装,同时会在计算成本上产生额外的开销。 ### 回答3: 基于Matlab的YALMIP是一个用于建模和求解优问题的开源软件包,它允许用户在Matlab环境中轻松地定义和求解各种数学问题,包括线性和非线性规划,半定规划和凸优等问题。Cplex是一种商业求解器,用于解决各种优问题,包括线性规划,混合整数规划,非线性规划和半定规划等问题。YALMIP可以与Cplex集成使用,实现更高效地求解优问题。 通过使用Cplex求解器,用户可以处理大规模的优问题,而不需要担心性能和内存的限制。同时,Cplex还支持多线程运算,可以大幅缩短求解时间,提高求解的效率。在使用YALMIP与Cplex集成求解优问题时,用户只需在Matlab中编写相应的模型代码,然后将模型传递给YALMIP,该软件将自动将问题转换为符合Cplex求解器的格式,然后启动Cplex求解器进行求解。求解完毕后,Cplex将结果返回给YALMIP,用户可以轻松地分析优的结果并进行后续的操作。 总之,基于Matlab的YALMIP与Cplex的集成使用可以帮助用户更快速、高效地求解各种数学问题,包括处理大规模优问题。用户可以充分利用其高效的求解能力进行各种应用领域的研究和开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值