2011年2月27日日曜日

shared_ptrとintrusive_ptrをメモリプールをつかって高速化してみる

引き続き、C++のメモリ動的確保の話。
shared_ptrをどうしても高速で扱えるようになりたかったので、メモリプールを組み合わせて試してみた。

まんまこちらのサイトを参考にさせてもらった。



#include <iostream>
#include <boost/intrusive_ptr.hpp>
#include <boost/shared_ptr.hpp>

#include <boost/pool/pool.hpp>
#include <new> // std::bad_alloc用

#include <time.h>
#include <sys/time.h>

class testClass {
private:
 static boost::pool<> p;
 
public:
 testClass():refCount(0) {}
 ~testClass()   {}
 
 static inline boost::intrusive_ptr<testClass>  createInPtr()
 {
  return boost::intrusive_ptr<testClass>(new_testClass());
 }
 
 static inline boost::shared_ptr<testClass>     createShPtr()
 {
  //第二引数でdelete用の関数を渡せる
  return boost::shared_ptr<testClass>(new_testClass(),testClass::delete_testClass);
 }
 
 static inline testClass* new_testClass()
 {
  testClass* temp = (testClass*)p.malloc();
  if (!temp) throw std::bad_alloc(); // メモリ確保に失敗した際、poolは0を返すため例外はこちらで投げとく
  return new(temp)testClass();       // 配置new
 }
 
 static inline void delete_testClass(testClass *ptr)
 {
  ptr->~testClass(); // freeはデストラクタを呼んでくれないため自前で呼ぶ必要あり
  p.free(ptr);
 }
 
private:
 
 // intrusive_ptr用 参照カウンタ
 int refCount;
 
 // intrusive_ptr用 参照が増えた時呼ばれる処理
 friend void intrusive_ptr_add_ref( testClass* ptr )
 {
  ptr->refCount++; 
 }
    
 // intrusive_ptr用 参照が減った時呼ばれる処理 参照が0になるとdeleteが呼ばれる。
 friend void intrusive_ptr_release( testClass* ptr )
 { 
  ptr->refCount--;
  if (ptr->refCount <= 0) delete_testClass(ptr);
 }
 
};

// pool の初期化
boost::pool<> testClass::p(sizeof(testClass));

typedef boost::intrusive_ptr<testClass> inPtr;
typedef boost::shared_ptr<testClass> shPtr; 

// 時間計測用
double gettimeofday_sec()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec + (double)tv.tv_usec*1e-6;
}

int main () {
 int max_count = 1000000;
 double start, end;
 
 // 比較用 ふつーに new delete ////////////// 
 start = gettimeofday_sec();
 for (int i = 0; i < max_count; i++){
  testClass *tp = new testClass();
  delete tp;
 }
 end   = gettimeofday_sec();
 printf("%f new delete\n", end - start);
 
 // shared_ptr /////////////////////////// 
 start = gettimeofday_sec();
 for (int i = 0; i < max_count; i++){
  shPtr tcShp = testClass::createShPtr();
 }
 end   = gettimeofday_sec();
 printf("%f shared_ptr\n", end - start);
 
 // intrusive_ptr ///////////////////////
 start = gettimeofday_sec();
 for (int i = 0; i < max_count; i++){
  inPtr tcInp = testClass::createInPtr();
 }
 end   = gettimeofday_sec();
 printf("%f intrusive_ptr\n", end - start);
 
    return 0;
}

実行速度は以下の通り
0.060343 new delete
0.080444 shared_ptr
0.003590 intrusive_ptr
intrusive_ptrに関してはかなり高速。
shared_ptrは上記のサイトにあるように参照カウンタ分のオーバヘッドが残っているため通常のnew deleteとトントンくらい。

intrusive_ptrの速度は魅力的だが、shared_ptrと対でつかうweak_ptrを使用したいのでこれで我慢しとく。

この書き方だと、メモリはrelease_memory()を呼んでやらない限りプールされ続ける。そのため、数回しかそのクラスを使わない場合は、メモリ効率が良くなさそう。けど、毎フレーム大量かつ一定の量クラスのメモリ確保と解放を行うような場合は使えそうだ。

あと、現時点でスレッドセーフという考え方を全く理解してないため、上のコードも安全かどうかさっぱり分かってない。そういった理由から、singleton_poolより単純に早かったstaticなpoolを使ってたりする。まあ、もう少し勉強したらもう一回チャレンジしてみよう。

0 件のコメント:

コメントを投稿