C++内存池的管理

转自
http://www.cnblogs.com/rosesmall/archive/2012/04/27/2473931.html
http://www.codeproject.com/Articles/15527/C-Memory-Pool
http://www.codeproject.com/Articles/27487/Why-to-use-memory-pool-and-how-to-implement-it

优先推荐第三个实例:
具体思路如下:
    1.首先申请一整块比较大的内存;
    2.把一大块内存切割成等份的n块内存池;
    3.管理未使用内存池列表和已使用内存池列表;
        3.1 当使用时把内存块从未使用列表放到已使用列表;
        3.2 当释放时把内存块从已使用列表放到未使用列表;
    4.当内存不足或者没有合适的内存块时,重新申请一块内存;
    5.对象使用结束,一次性释放所有的内存;

先看一个概念图:



全部代码参考附件 MemoryPoolSourceCode.zip
#ifndef __MEMPOOL_H__
#define __MEMPOOL_H__
class CMemPool
{
private:
	struct _Unit							//The type of the node of linkedlist.
	{
		struct _Unit *pPrev, *pNext;
	};

	void*			m_pMemBlock;			//The address of memory pool.

	//Manage all unit with two linkedlist.
    struct _Unit*	m_pAllocatedMemBlock;	//Head pointer to Allocated linkedlist.
	struct _Unit*	m_pFreeMemBlock;		//Head pointer to Free linkedlist.

    unsigned long	m_ulUnitSize;			//Memory unit size. There are much unit in memory pool.
    unsigned long	m_ulBlockSize;			//Memory pool size. Memory pool is make of memory unit.

public:
	CMemPool(unsigned long lUnitNum = 50, unsigned long lUnitSize = 1024);
	~CMemPool();
	
    void*           Alloc(unsigned long ulSize, bool bUseMemPool = true);	//Allocate memory unit
    void            Free( void* p );										//Free memory unit
};

#endif //__MEMPOOL_H__


#include <stdio.h>
#include <malloc.h>
#include "MemPool.h"

/*==============================================================================
CMemPool:
	Constructor of this class. It allocate memory block from system and create
	a static double linked list to manage all memory unit.

Parameters:
	[in]ulUnitNum
	The number of unit which is a part of memory block.

	[in]ulUnitSize
	The size of unit.
//=============================================================================
*/
CMemPool::CMemPool(unsigned long ulUnitNum,unsigned long ulUnitSize) :
    m_pMemBlock(NULL), m_pAllocatedMemBlock(NULL), m_pFreeMemBlock(NULL), 
    m_ulBlockSize(ulUnitNum * (ulUnitSize+sizeof(struct _Unit))), 
    m_ulUnitSize(ulUnitSize)
{
	
	m_pMemBlock = ::malloc(m_ulBlockSize);			//Allocate a memory block.
	
	if(NULL != m_pMemBlock)
	{
		for(unsigned long i=0; i<ulUnitNum; i++)	//Link all mem unit .
		{
			struct _Unit *pCurUnit = (struct _Unit *)( (char *)m_pMemBlock + i*(ulUnitSize+sizeof(struct _Unit)) );
			
			pCurUnit->pPrev = NULL;
			pCurUnit->pNext = m_pFreeMemBlock;		//Insert the new unit at head.
			
			if(NULL != m_pFreeMemBlock)
			{
				m_pFreeMemBlock->pPrev = pCurUnit;
			}
			m_pFreeMemBlock = pCurUnit;
		}
	}	
}

/*==============================================================================
~CMemPool():
	Destructor of this class. Its task is to free memory block.
//=============================================================================
*/
CMemPool::~CMemPool()
{
	::free(m_pMemBlock);
}


/*==============================================================================
Alloc:
	To allocate a memory unit. If memory pool can`t provide proper memory unit,
	will call system function.

Parameters:
	[in]ulSize
	Memory unit size.

	[in]bUseMemPool
	Whether use memory pool.

Return Values:
	Return a pointer to a memory unit.
//=============================================================================
*/	
void* CMemPool::Alloc(unsigned long ulSize, bool bUseMemPool)
{
	if(	ulSize > m_ulUnitSize || false == bUseMemPool || 
		NULL == m_pMemBlock   || NULL == m_pFreeMemBlock)
	{
		return malloc(ulSize);
	}

	//Now FreeList isn`t empty
	struct _Unit *pCurUnit = m_pFreeMemBlock;

	m_pFreeMemBlock = pCurUnit->pNext;			//Get a unit from free linkedlist.
	if(NULL != m_pFreeMemBlock)
	{
		m_pFreeMemBlock->pPrev = NULL;
	}

	pCurUnit->pNext = m_pAllocatedMemBlock;
	
	if(NULL != m_pAllocatedMemBlock)
	{
		m_pAllocatedMemBlock->pPrev = pCurUnit; 
	}
	m_pAllocatedMemBlock = pCurUnit;

	return (void *)((char *)pCurUnit + sizeof(struct _Unit) );
}


/*==============================================================================
Free:
	To free a memory unit. If the pointer of parameter point to a memory unit,
	then insert it to "Free linked list". Otherwise, call system function "free".

Parameters:
	[in]p
	It point to a memory unit and prepare to free it.

Return Values:
	none
//=============================================================================
*/
void CMemPool::Free( void* p )
{
	if(m_pMemBlock<p && p<(void *)((char *)m_pMemBlock + m_ulBlockSize) )
	{
		struct _Unit *pCurUnit = (struct _Unit *)((char *)p - sizeof(struct _Unit) );

		m_pAllocatedMemBlock = pCurUnit->pNext;
		if(NULL != m_pAllocatedMemBlock)
		{
			m_pAllocatedMemBlock->pPrev = NULL;
		}

		pCurUnit->pNext = m_pFreeMemBlock;
		if(NULL != m_pFreeMemBlock)
		{
			m_pFreeMemBlock->pPrev = pCurUnit;
		}

		m_pFreeMemBlock = pCurUnit;
	}
	else
	{
		free(p);
	}
}


Test Code
#include <Windows.h>
#include <iostream>
#include "..\MemoryPool\MemoryPool.h"
using namespace std;

//
//////////////////////////////////////////////////////////////////////////
unsigned int g_nTotalRun = 100;
unsigned int g_nCycleCount = 1000000/*0x5fffff*/;

//CTestClass
//////////////////////////////////////////////////////////////////////////
class CTestClassNoPool
{
public:
    char m_chBuf[1024];
};

void CCTestClassNoPool_RunTesting()
{
    DWORD count = GetTickCount();

    for(unsigned int i=0; i<g_nCycleCount; i++)
    {
        CTestClassNoPool *p = new CTestClassNoPool;
        delete p;
    }

    cout << "[Repeat " << g_nCycleCount << " Times]" << "[No pool] Interval = " << GetTickCount()-count << " ms" << endl;
}

//
//////////////////////////////////////////////////////////////////////////
char buf[4100];  //Simple Memory Pool 

class CTestClassSimplePool
{
    char m_chBuf[1024];

public:
//     CTestClassSimplePool()
//     {
//         cout << "CTestClassSimplePool" << endl;
//     }

    void *operator new(unsigned int uiSize)
    {
        return (void *)buf;
    }
    void  operator delete(void *p)
    {
    }
};

void CTestClassSimplePool_RunTesting()
{
    DWORD count = GetTickCount();

    for(unsigned int i=0; i<g_nCycleCount; i++)
    {
        CTestClassSimplePool *p = new CTestClassSimplePool; //Didn't use system new operator!
        delete p;
    }

    cout << "[Repeat " << g_nCycleCount << " Times]" << "[Using Simple pool] Interval = " << GetTickCount()-count << " ms" << endl;
}



//////////////////////////////////////////////////////////////////////////
class CTestClassMemoryPool
{
public:
//     CTestClassMemoryPool()
//     {
//         cout << "CTestClassMemoryPool" << endl;
//     }
    //*
    void *operator new(unsigned int uiSize);    //Must overload the function. It`s static function.
    void  operator delete(void *p);                //Must overload the function. It`s static function.
    //*/
    
private:
    char m_chBuf[1024];
};

CMemoryPool g_MemPool(10,1024);

//*
void *CTestClassMemoryPool::operator new(unsigned int uiSize)
{
    return g_MemPool.Alloc(uiSize);
}

void  CTestClassMemoryPool::operator delete(void *p)
{
    g_MemPool.Free(p);
}

void * operator new [](unsigned int uiSize)
{
   return g_MemPool.Alloc(uiSize);
}  

void operator delete[](void *p)
{
    g_MemPool.Free(p);
}

void CTestClassMemoryPool_RunTesting()
{
    DWORD count = GetTickCount();
    for(unsigned int i=0; i< g_nCycleCount; i++)
    {
        //class CTestClass and class CTestClass2 are almost same!
        CTestClassMemoryPool *p = new CTestClassMemoryPool;        //Using memory pool
        delete p;
    }

    cout << "[Repeat " << g_nCycleCount << " Times]" << "[Using memory pool] Interval = " << GetTickCount()-count << " ms" << endl;
}


//////////////////////////////////////////////////////////////////////////
void main()
{
    cout << "How much new operations for each test case?" << endl;
    cin  >> g_nCycleCount;
    cout << "How many times do you want to running?" << endl;
    cin  >> g_nTotalRun;

    for (unsigned int i = 0 ; i< g_nTotalRun; i++)
    {
        cout << "----------------" << i+1 << "------------------" << endl;
        CCTestClassNoPool_RunTesting();
        CTestClassSimplePool_RunTesting();
        CTestClassMemoryPool_RunTesting();
    }

//     CTestClassMemoryPool *p = new CTestClassMemoryPool[2];        //Using memory pool
//     delete[] p;
}





译者点评:一个简单的内存池实现,附有源码,简单易懂,适合入门。



概述

在c/c++中,内存分配(如malloc或new)会使用很多时间。

一个程序会随着长时间的运行和内存的申请释放而变得越来越慢,内存也会随着时间逐渐碎片化。特别是高频率的进行小内存申请释放,此问题变得尤其严重。



解决方案:定制内存池

为解决上述问题,一个(可能的)的解决方案就是使用内存池。



“内存池”在初始化时,分配一个大块内存(称 原始内存块),并且将此内存分割为一些小的内存块。当你需要请求分配内存时,则从内存池中取出事先分配好的内存,而不是向OS申请。内存池最大的优势在于:

1、极少的(甚至没有)堆碎片整理

2、较之普通内存分配(如malloc,new),有着更快的速度



额外的,你还将获得如下好处:

1、检测任意的指针是否指向内存池内

2、生成"heap-dump"

3、各种 内存泄漏 检测:当你没有释放之前申请的内存,内存池将抛出断言



如何工作?

让我们看看内存池的UML模型图:


图中简要的描述了CMemoryPool class,更多的细节请查看源码中class声明。



那么,CMemoryPool如何实际工作?



关于 MemoryChunks

正如你在UML图中所看到的,内存池维护着一个SMemoryChunk链表,并管理着三个指向SMemoryChunk结构的指针(m_ptrFirstChunk, m_ptrLastChunk, and m_ptrCursorChunk)。这些指针指向SMemoryChunk链表的不同位置。让我们更深入的观察SMemoryChunk:(在内存池实现中,SMemoryChunk封装了原始内存块的各个部分 -- 译者注)

typedef struct SMemoryChunk
{
  TByte *Data ;             // 常规数据指针
  std::size_t DataSize ;    // 内存块容量
  std::size_t UsedSize ;    // 内存块当前使用大小
  bool IsAllocationChunk ;  // 为true时, 内存块已被分配,可用free之类的函数释放
  SMemoryChunk *Next ;      // 指向内存块链表中的下一个内存块,可能为null

第一步:预分配内存
当你调用CMemoryPool的构造函数,内存池会向OS申请原始内存块。
/******************
Constructor
******************/
CMemoryPool::CMemoryPool(const std::size_t &sInitialMemoryPoolSize,
                         const std::size_t &sMemoryChunkSize,
                         const std::size_t &sMinimalMemorySizeToAllocate,
                         bool bSetMemoryData)
{
  m_ptrFirstChunk  = NULL ;
  m_ptrLastChunk   = NULL ;
  m_ptrCursorChunk = NULL ;
  m_sTotalMemoryPoolSize = 0 ;
  m_sUsedMemoryPoolSize  = 0 ;
  m_sFreeMemoryPoolSize  = 0 ;
  m_sMemoryChunkSize   = sMemoryChunkSize ;
  m_uiMemoryChunkCount = 0 ;
  m_uiObjectCount      = 0 ;
  m_bSetMemoryData               = bSetMemoryData ;
  m_sMinimalMemorySizeToAllocate = sMinimalMemorySizeToAllocate ;
  // Allocate the Initial amount of Memory from the Operating-System...
  AllocateMemory(sInitialMemoryPoolSize) ;
}
所有的成员的函数初始化在此完成,最后AllocateMemory将完成向OS申请原始内存块的任务。
/******************
AllocateMemory
******************/
<CODE>bool CMemoryPool::AllocateMemory(const std::size_t &sMemorySize)
{
  std::size_t sBestMemBlockSize = CalculateBestMemoryBlockSize(sMemorySize) ;
  // allocate from Operating System
  TByte *ptrNewMemBlock = (TByte *) malloc(sBestMemBlockSize) ;
  ...
那么,内存池如何来管理这些数据呢?

第二步:内存分块
回忆前述,内存池管理使用SMemoryChunk链表来管理数据。在向OS申请原始内存块后,
我们还没有在其上建立SMemoryChunk。

图中所示的为初始化分配后的内存池。

我们需要分配一组SMemoryChunk,用于管理原始内存块:
//(AllocateMemory() continued) :
  ...
  unsigned int uiNeededChunks = CalculateNeededChunks(sMemorySize) ;
  // allocate Chunk-Array to Manage the Memory
  SMemoryChunk *ptrNewChunks =
    (SMemoryChunk *) malloc((uiNeededChunks * sizeof(SMemoryChunk))) ;
  assert(((ptrNewMemBlock) && (ptrNewChunks))
                           && "Error : System ran out of Memory") ;
  ...
CalculateNeededChunks函数用于计算需要分配的SMemoryChunk的数量。分配后,ptrNewChunks指向这组SMemoryChunk。注意,SMemoryChunk中目前只是持有垃圾数据,我们还没有为SMemoryChunk的成员关联至原始内存块。



最后,AllocateMemory函数将为所有的SMemoryChunk关联至原始内存块。

//(AllocateMemory() continued) :
  ...
  // Associate the allocated Memory-Block with the Linked-List of MemoryChunks
  return LinkChunksToData(ptrNewChunks, uiNeededChunks, ptrNewMemBlock) ;

让我们进入LinkChunksToData中一窥究竟:
/******************
LinkChunksToData
******************/
bool CMemoryPool::LinkChunksToData(SMemoryChunk *ptrNewChunks,
     unsigned int uiChunkCount, TByte *ptrNewMemBlock)
{
  SMemoryChunk *ptrNewChunk = NULL ;
  unsigned int uiMemOffSet = 0 ;
  bool bAllocationChunkAssigned = false ;
  for(unsigned int i = 0; i < uiChunkCount; i++)
  {
    if(!m_ptrFirstChunk)
    {
      m_ptrFirstChunk = SetChunkDefaults(&(ptrNewChunks[0])) ;
      m_ptrLastChunk = m_ptrFirstChunk ;
      m_ptrCursorChunk = m_ptrFirstChunk ;
    }
    else
    {
      ptrNewChunk = SetChunkDefaults(&(ptrNewChunks[i])) ;
      m_ptrLastChunk->Next = ptrNewChunk ;
      m_ptrLastChunk = ptrNewChunk ;
    }
   
    uiMemOffSet = (i * ((unsigned int) m_sMemoryChunkSize)) ;
    m_ptrLastChunk->Data = &(ptrNewMemBlock[uiMemOffSet]) ;
    // 第一个SMemoryChunk被称为“AllocationChunk”。
    // 这意味着,它持有原始内存块的指针并能够利用它释放原始内存块
    if(!bAllocationChunkAssigned)
    {
      m_ptrLastChunk->IsAllocationChunk = true ;
      bAllocationChunkAssigned = true ;
    }
  }
  return RecalcChunkMemorySize(m_ptrFirstChunk, m_uiMemoryChunkCount) ;
}
让我们一步步的来看这个重要的函数:第一行检查在SMemoryChunk链表中是否已经有了可用的
SMemoryChunk:
  ...
  if(!m_ptrFirstChunk)
  ...
在最初始进入循环,此条件不成立。那么,我们为一些内部的成员进行关联。

...
  m_ptrFirstChunk = SetChunkDefaults(&(ptrNewChunks[0])) ;
  m_ptrLastChunk = m_ptrFirstChunk ;
  m_ptrCursorChunk = m_ptrFirstChunk ;
  ...
m_ptrFirstChunk这时指向SMemoryChunk中的第一个元素。每一个SMemoryChunk管理的内存块大小由m_sMemoryChunkSize指定。这些内存块来自于原始内存块,偏移量
uiMemOffSet指示着每一个SMemoryChunk所管理的内存起始点处于原始内存块的何处。
  uiMemOffSet = (i * ((unsigned int) m_sMemoryChunkSize)) ;
  m_ptrLastChunk->Data = &(ptrNewMemBlock[uiMemOffSet]) ;
额外的,每个新的SMemoryChunk都将被指定为新的m_ptrLastChunk。
  ...
  m_ptrLastChunk->Next = ptrNewChunk ;
  m_ptrLastChunk = ptrNewChunk ;
  ...
经过循环之后,内存池中的SMemoryChunk链表将被成功的与原始内存块关联。
最终,我们重新计算每一个SMemoryChunk能管理到的内存尺寸。这个步骤相当耗时,并且必须在每次从OS附加新的内存到内存池后调用。所有被计算出的尺寸,将被DataSize成员持有。

/******************
RecalcChunkMemorySize
******************/
bool CMemoryPool::RecalcChunkMemorySize(SMemoryChunk *ptrChunk,
                  unsigned int uiChunkCount)
{
  unsigned int uiMemOffSet = 0 ;
  for(unsigned int i = 0; i < uiChunkCount; i++)
  {
    if(ptrChunk)
    {
      uiMemOffSet = (i * ((unsigned int) m_sMemoryChunkSize)) ;
      ptrChunk->DataSize =
        (((unsigned int) m_sTotalMemoryPoolSize) - uiMemOffSet) ;
      ptrChunk = ptrChunk->Next ;
    }
    else
    {
     assert(false && "Error : ptrChunk == NULL") ;
     return false ;
    }
  }
  return true ;
}
在RecalcChunkMemorySize之后,每一个SMemoryChunk将知道自己需要释放多大的内存。因此,这使得 确定某个SMemoryChunk能否持有一个指定大小的内存 将变得非常容易:当DataSize成员大于或等于请求的内存尺寸并且UsedSize成员值为0,这时此SMemoryChunk将能够满足用户的需要。让我们来看一个具体的例子来加深对这个机制的理解,假设内存池为600字节,并且每个SMemoryChunk为100字节。


第三步:向内存池请求内存

现在,如果用户向内存池请求内存,那会发生什么呢?最开始,所有的SMemoryChunk在内存池中都是闲置可用状态:


让我们看看GetMemory函数吧:

/******************
GetMemory
******************/
void *CMemoryPool::GetMemory(const std::size_t &sMemorySize)
{
  std::size_t sBestMemBlockSize = CalculateBestMemoryBlockSize(sMemorySize) ; 
  SMemoryChunk *ptrChunk = NULL ;
  while(!ptrChunk)
  {
    // 搜索是否有符合条件的SMemoryChunk?
    ptrChunk = FindChunkSuitableToHoldMemory(sBestMemBlockSize) ;
    if(!ptrChunk)
    {
  // 没有SMemoryChunk符合条件
  // 内存池太小了,需要向OS申请新的内存
  sBestMemBlockSize = MaxValue(sBestMemBlockSize, CalculateBestMemoryBlockSize(m_sMinimalMemorySizeToAllocate)) ;
      AllocateMemory(sBestMemBlockSize) ;
    }
  }
  // 一个合适的SMemoryChunk被找到
  // 校正其 TotalSize/UsedSize 成员的值
  m_sUsedMemoryPoolSize += sBestMemBlockSize ;
  m_sFreeMemoryPoolSize -= sBestMemBlockSize ;
  m_uiObjectCount++ ;
  SetMemoryChunkValues(ptrChunk, sBestMemBlockSize) ;
  // 最终将内存指针返回给用户
  return ((void *) ptrChunk->Data) ;
}
当用户向内存池发出请求,内存池搜索SMemoryChunk链表,并在其中找到满足条件的SMemoryChunk,“满足条件”意味着:

1、DataSize必须大于或等于请求的大小

2、UsedSize必须为0

FindChunkSuitableToHoldMemory如果其返回NULL,那么就表示在内存池中没有可用的内存。这将会引发AllocateMemory函数的调用(前述),此函数会向OS申请更多的内存。

如果返回非NULL,那么便找到了可用的SMemoryChunk。



示例

假设,用户向内存池申请250字节:


如你所见,每一个SMemoryChunk管理100字节,所以,250字节并不是100的整数倍。这会引发什么情况呢?GetMemory将会返回指向第一个SMemoryChunk的指针,并设置其的UsedSize成员为300字节,因为300是100的整数倍数值中最小的,并且其大于250。多出的50字节称为"memory overhead".

当FindChunkSuitableToHoldMemory寻找可用的SMemoryChunk时,它将只会从一个闲置的SMemoryChunk跳到另一个闲置的SMemoryChunk。这意味着,如果又有申请内存的请求达到,例子中的第四个SMemoryChunk将是寻找的起始点。



如何使用代码

代码的使用简单而直接:

只需要在你的程序中包含"CMemoryPool.h",并附加源码文件至你的IDE/makefile:

CMemoryPool.h
CMemoryPool.cpp
IMemoryBlock.h
SMemoryChunk.h
你需要创建一个CMemoryPool实例,并从中分配内存。所有的内存池配置都在CMemoryPool的构造函数中被完成。



使用示例

MemPool::CMemoryPool *g_ptrMemPool = new MemPool::CMemoryPool() ;
char *ptrCharArray = (char *) g_ptrMemPool->GetMemory(100) ;
...
g_ptrMemPool->FreeMemory(ptrCharArray, 100) ;
delete g_ptrMemPool ;

兴趣点

内存诊断
你可以调用WriteMemoryDumpToFile函数来输出内存诊断信息文件。让我们看下源码附带的MyTestClass_OPOverload类的构造函数。(此类重载了new和delete操作,使用了内存池操作)
MyTestClass_OPOverload()
{
  m_cMyArray[0] = 'H' ;
  m_cMyArray[1] = 'e' ;
  m_cMyArray[2] = 'l' ;
  m_cMyArray[3] = 'l' ;
  m_cMyArray[4] = 'o' ;
  m_cMyArray[5] = NULL ;
  m_strMyString = "This is a small Test-String" ;
  m_iMyInt = 12345 ;
  m_fFloatValue = 23456.7890f ;
      m_fDoubleValue = 6789.012345 ;
      Next = this ;
}

MyTestClass *ptrTestClass = new MyTestClass ;
g_ptrMemPool->WriteMemoryDumpToFile("MemoryDump.bin") ;

让我们看看内存诊断文件的内容:


如你所见,这是MyTestClass_OPOverload所有的成员在内存中的表示。

速度测试
我在windows下完成了一个简单的速度测试(使用timeGetTime()),结果显示内存池的使用可以大大增加程序的速度。所有的测试均使用vs2003,debug模式编译(测试机器:Intel Pentium IV Processor (32 bit), 1GB RAM, MS Windows XP Professional)

//Array-test (Memory Pool):
for(unsigned int j = 0; j < TestCount; j++)
{
    // ArraySize = 1000
    char *ptrArray = (char *) g_ptrMemPool->GetMemory(ArraySize)  ;
    g_ptrMemPool->FreeMemory(ptrArray, ArraySize) ;
}
//Array-test (Heap):
for(unsigned int j = 0; j < TestCount; j++)
{
    // ArraySize = 1000
    char *ptrArray = (char *) malloc(ArraySize)  ;
    free(ptrArray) ;
}



//Class-Test for MemoryPool and Heap (重载了new与delete)
for(unsigned int j = 0; j < TestCount; j++)
{
    MyTestClass *ptrTestClass = new MyTestClass ;
    delete ptrTestClass ;
}



关于代码

代码在ms windows与linux的如下c++编译器通过测试:

Microsoft Visual C++ 6.0
Microsoft Visual C++ .NET 2003
MinGW (GCC) 3.4.4 (Windows)
GCC 4.0.X (Debian GNU Linux)
vc6.0的项目文件与vs2003的项目文件已经包含在源码中。在64位的环境下使用应该没有问题。

注意:此内存池并非线程安全的。



待办事项

此内存池实现远远不够完善,待办事项如下:

1、对于海量的内存,memory overhead可能很大

2、一些CalculateNeededChunks函数的调用可以通过重构某些函数来被剥离,之后速度可能会更快。

3、更多的稳定性测试(尤其是对长时间运行的程序)

4、线程安全的实现

猜你喜欢

转载自jacky-dai.iteye.com/blog/2310340