Poco::MemoryPool

Poco::MemoryPool を紹介します。

Poco::MemoryPool は、固定サイズのメモリブロックのプールで、メモリブロックのアロケーションの高速化が主な目的です。アロケートされたメモリブロックは、利用されるまで retain されます。メモリブロックの最大数が指定でき、プリアロケートしておくこともできます。

MemoryPoolTest.cpp

・TestBasics() で基本的な動作を例示。
・256byte のブロックを 4 つ、プリアロケートせずに確保。
・1 ブロックずつ allocate/release/allocate し、allocated()available() を表示。
・TestSpeed() で動作速度を測定。
・256byte のブロックを、最大 1024×1024 個確保。(256MBytes)
・new/delete と Poco::MemoryPool のプリアロケート無し、有りの 3 者を比較。
・MeasureAllocDeallocTime というテンプレートクラスを作り、Init(), Cleanup(), Alloc(), Dealloc()
 を Poco::MemoryPool に対するテンプレートの特殊化で対応。
・new/delete の時は、テンプレートに Poco::Void を渡して対応。
・allocate/deallocate(release) を 3 回繰り返し、それぞれにかかった時間と、初期化/終了時/トータル
 の所要時間を測定。

#include <Poco/Format.h>
#include <Poco/MemoryPool.h>
#include <Poco/Stopwatch.h>
#include <Poco/Void.h>
 
#include <string>
 
#include "ScopedLogMessage.h"
#include "PrepareConsoleLogger.h"
 
const std::size_t kBlockSize = 256;
const int kNumMaxAlloc_Basics = 4;
const int kNumMaxAlloc_Speed = 1024*1024;
const std::size_t kNumLoop = 3;
 
typedef struct
{
	double timeTotal;
	double timeInit;
	double timeCleanup;
	double timeAlloc[kNumLoop];
	double timeDealloc[kNumLoop];
} ElapsedTimeRec;
 
void ShowMemoryPoolAttr(ScopedLogMessage& msg, const Poco::MemoryPool& pool)
{
	msg.Message(Poco::format("  blockSize()=%z, allocated()=%d, available()=%d", pool.blockSize(), pool.allocated(), pool.available()));
}
 
void TestBasics(ScopedLogMessage& msg)
{
	msg.Message("--- TestBasics ---");
 
	Poco::MemoryPool pool(kBlockSize, 0, kNumMaxAlloc_Basics);
	msg.Message(Poco::format(" Poco::MemoryPool(%z, %d, %d)", kBlockSize, 0, kNumMaxAlloc_Basics));
	ShowMemoryPoolAttr(msg, pool);
 
	std::vector<void*> ptrs;
	msg.Message(" pool.get()");
	for(int i=0; i<kNumMaxAlloc_Basics; ++i)
	{
		ptrs.push_back(pool.get());
		ShowMemoryPoolAttr(msg, pool);
	}
	try
	{
		pool.get();
	}
	catch(Poco::OutOfMemoryException&)
	{
		msg.Message(Poco::format(" calling pool.get() more than %d fails", kNumMaxAlloc_Basics));
	}
	msg.Message(" pool.release()");
	for(int i=0; i<kNumMaxAlloc_Basics; ++i)
	{
		pool.release(ptrs[i]);
		ShowMemoryPoolAttr(msg, pool);
	}
	msg.Message(" pool.get() again");
	for(int i=0; i<kNumMaxAlloc_Basics; ++i)
	{
		ptrs.push_back(pool.get());
		ShowMemoryPoolAttr(msg, pool);
	}
}
 
template <class T>
class MeasureAllocDeallocTime
{
public:
	MeasureAllocDeallocTime(ElapsedTimeRec& timeRec, std::size_t blockSize, int preAlloc, int maxAlloc) :
		m_pPool		(NULL)
	{
		std::vector<char*> ptrs(maxAlloc);
		{
			Poco::Stopwatch sw_total;
			sw_total.start();
			{	// initialize
				Poco::Stopwatch sw_init;
				sw_init.start();
				Init(blockSize, preAlloc, maxAlloc);
				sw_init.stop();
				timeRec.timeInit = (1000.0 * sw_init.elapsed()) / sw_init.resolution();
			}
			for(std::size_t loop=0; loop<kNumLoop; ++loop)
			{
				{	// allocate
					Poco::Stopwatch sw_alloc;
					sw_alloc.start();
					for(int i=0; i<maxAlloc; ++i)
					{
						ptrs[i] = Alloc(blockSize);
					}
					sw_alloc.stop();
					timeRec.timeAlloc[loop] = (1000.0 * sw_alloc.elapsed()) / sw_alloc.resolution();
				}
				{	// deallocate
					Poco::Stopwatch sw_dealloc;
					sw_dealloc.start();
					for(int i=0; i<maxAlloc; ++i)
					{
						Dealloc(ptrs[i]);
					}
					sw_dealloc.stop();
					timeRec.timeDealloc[loop] = (1000.0 * sw_dealloc.elapsed()) / sw_dealloc.resolution();
				}
			}
			{	// cleanup
				Poco::Stopwatch sw_cleanup;
				sw_cleanup.start();
				Cleanup();
				sw_cleanup.stop();
				timeRec.timeCleanup = (1000.0 * sw_cleanup.elapsed()) / sw_cleanup.resolution();
			}
			sw_total.stop();
			timeRec.timeTotal = (1000.0 * sw_total.elapsed()) / sw_total.resolution();
		}
	}
	~MeasureAllocDeallocTime()
	{
	}
 
private:
	MeasureAllocDeallocTime() :
		m_pPool		(NULL)
	{
	}
	void Init(std::size_t /*blockSize*/, int /*preAlloc*/, int /*maxAlloc*/)
	{
	}
	void Cleanup(void)
	{
	}
	char* Alloc(std::size_t blockSize)
	{
		return new char[blockSize];
	}
	void Dealloc(char* ptr)
	{
		delete [] ptr;
	}
 
	T*	m_pPool;
};
 
template <>
void MeasureAllocDeallocTime<Poco::MemoryPool>::Init(std::size_t blockSize, int preAlloc, int maxAlloc)
{
	m_pPool = new Poco::MemoryPool(blockSize, preAlloc, maxAlloc);
}
template <>
void MeasureAllocDeallocTime<Poco::MemoryPool>::Cleanup(void)
{
	if(NULL != m_pPool)
	{
		delete m_pPool;
		m_pPool = NULL;
	}
}
template <>
char* MeasureAllocDeallocTime<Poco::MemoryPool>::Alloc(std::size_t /*blockSize*/)
{
	return static_cast<char*>(m_pPool->get());
}
template <>
void MeasureAllocDeallocTime<Poco::MemoryPool>::Dealloc(char* ptr)
{
	m_pPool->release(ptr);
}
 
#if defined(POCO_OS_FAMILY_WINDOWS)
#include <new.h>
 
int ThrowBadAlloc(std::size_t)
{
	throw std::bad_alloc();
	return 0;
}
#endif
 
void TestSpeed(ScopedLogMessage& msg)
{
ElapsedTimeRec timeRec[3];
 
	msg.Message("--- TestSpeed ---");
 
	int numMaxAlloc = kNumMaxAlloc_Speed;
	{	// adjust number of max allocation for low memory machine
		bool memoryAvailable = false;
		char* tempPtr = NULL;
 
#if defined(POCO_OS_FAMILY_WINDOWS)
		_set_new_handler(ThrowBadAlloc);
#endif
 
		while(!memoryAvailable)
		{
			try
			{
				tempPtr = new char [kBlockSize * numMaxAlloc];
				memoryAvailable = true;
			}
			catch(...)
			{
				numMaxAlloc /= 2;
			}
			delete [] tempPtr;
			tempPtr = NULL;
		}
		msg.Message(Poco::format(" number of allocation blocks = %d", numMaxAlloc));
	}
 
	{
		msg.Message(" measuring new/delete...");
		MeasureAllocDeallocTime<Poco::Void> measure(timeRec[0], kBlockSize, 0, numMaxAlloc);
	}
	{
		msg.Message(Poco::format(" measuring Poco::MemoryPool(%z, %d, %d)..."
					, kBlockSize
					, 0
					, numMaxAlloc));
		MeasureAllocDeallocTime<Poco::MemoryPool>
			measure(timeRec[1], kBlockSize, 0, numMaxAlloc);
	}
	{
		msg.Message(Poco::format(" measuring Poco::MemoryPool(%z, %d, %d)..."
					, kBlockSize
					, numMaxAlloc
					, numMaxAlloc));
		MeasureAllocDeallocTime<Poco::MemoryPool>
			measure(timeRec[2], kBlockSize, numMaxAlloc, numMaxAlloc);
	}
 
	msg.Message("                 new/delete   non pre-allocated   pre-allocated");
	msg.Message("================================================================");
	msg.Message(Poco::format("  init          %8.3fmSec    %8.3fmSec      %8.3fmSec"
					, timeRec[0].timeInit
					, timeRec[1].timeInit
					, timeRec[2].timeInit));
	for(std::size_t loop=0; loop<kNumLoop; ++loop)
	{
		msg.Message(Poco::format("  allocate #%z   %8.3fmSec    %8.3fmSec      %8.3fmSec"
					, loop+1
					, timeRec[0].timeAlloc[loop]
					, timeRec[1].timeAlloc[loop]
					, timeRec[2].timeAlloc[loop]));
		msg.Message(Poco::format("deallocate #%z   %8.3fmSec    %8.3fmSec      %8.3fmSec"
					, loop+1
					, timeRec[0].timeDealloc[loop]
					, timeRec[1].timeDealloc[loop]
					, timeRec[2].timeDealloc[loop]));
	}
	msg.Message(Poco::format("  cleanup       %8.3fmSec    %8.3fmSec      %8.3fmSec"
					, timeRec[0].timeCleanup
					, timeRec[1].timeCleanup
					, timeRec[2].timeCleanup));
	msg.Message("================================================================");
	msg.Message(Poco::format("  total time    %8.3fmSec    %8.3fmSec      %8.3fmSec"
					, timeRec[0].timeTotal
					, timeRec[1].timeTotal
					, timeRec[2].timeTotal));
}
 
int main(int /*argc*/, char** /*argv*/)
{
	PrepareConsoleLogger logger(Poco::Logger::ROOT, Poco::Message::PRIO_INFORMATION);
 
	ScopedLogMessage msg("MemoryPoolTest ", "start", "end");
 
	TestBasics(msg);
	TestSpeed(msg);
 
	return 0;
}

Results of execution

・一旦 get() して release() するまで、available() が(直感的に)正しい値を返さないことに注意。
 「新たにアロケートすることなく使える個数」くらいの意味。
・トータルの所要時間は、new/delete > non pre-allocated > pre-allocated だが、non pre-allocated と
 pre-allocated との差が実使用状態であるかどうかは微妙なところ。

[0] MemoryPoolTest start
[0] --- TestBasics ---
[0]  Poco::MemoryPool(256, 0, 4)
[0]   blockSize()=256, allocated()=0, available()=0
[0]  pool.get()
[0]   blockSize()=256, allocated()=1, available()=0
[0]   blockSize()=256, allocated()=2, available()=0
[0]   blockSize()=256, allocated()=3, available()=0
[0]   blockSize()=256, allocated()=4, available()=0
[0]  calling pool.get() more than 4 fails
[0]  pool.release()
[0]   blockSize()=256, allocated()=4, available()=1
[0]   blockSize()=256, allocated()=4, available()=2
[0]   blockSize()=256, allocated()=4, available()=3
[0]   blockSize()=256, allocated()=4, available()=4
[0]  pool.get() again
[0]   blockSize()=256, allocated()=4, available()=3
[0]   blockSize()=256, allocated()=4, available()=2
[0]   blockSize()=256, allocated()=4, available()=1
[0]   blockSize()=256, allocated()=4, available()=0
[0] --- TestSpeed ---
[0]  number of allocation blocks = 1048576
[0]  measuring new/delete...
[0]  measuring Poco::MemoryPool(256, 0, 1048576)...
[0]  measuring Poco::MemoryPool(256, 1048576, 1048576)...
[0]                  new/delete   non pre-allocated   pre-allocated
[0] ================================================================
[0]   init             0.001mSec       0.015mSec        86.591mSec
[0]   allocate #1     84.544mSec     121.834mSec        40.697mSec
[0] deallocate #1    499.517mSec      64.868mSec        39.726mSec
[0]   allocate #2     83.324mSec      40.682mSec        40.683mSec
[0] deallocate #2    495.804mSec      39.706mSec        39.716mSec
[0]   allocate #3     83.412mSec      40.692mSec        40.680mSec
[0] deallocate #3    504.934mSec      39.738mSec        39.743mSec
[0]   cleanup          0.000mSec     499.142mSec       486.371mSec
[0] ================================================================
[0]   total time    1751.557mSec     846.687mSec       814.214mSec
[0] MemoryPoolTest end

Downloads

ここをクリックすると、makefile や VC++ プロジェクトなど一式がダウンロードできます。
(2013.05.31 updated)
・2010年6月14日からのダウンロード数:768

Subversion

・フリーの Subversion ホスティングサービス Assemblaで、ソースコードを管理しています。

Reference

http://pocoproject.org にある MemoryManagement のプレセンテーション。(PDF)

Powered by POCO Copyright © 2010 Round Square Inc. All rights reserved.


Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>