Boost.Poolを試してみた

ゲームで使いたいBoost.Pool…だけど聞こえてくる噂はどうにも微妙…?
全ゲ連の講演とかでも時折聞いたりするものの、イマイチ「いいよ!! マジおすすめ!」っていう程のトーンで話を聞かない。
そんな中、ゲームプログラミングにおけるC++の都市伝説Twitterで流れてきたので読んでいたら、なんとBoost.Poolの話があった!
それで、いろいろ納得できたり、もうちょっと挙動知りたかったりだったので、自分で試してみた。


Boost.Poolは内部で確保したメモリ領域を小分けにして使うことでnewが早く………
とか、よく聞くのだけれど、つまりは
「事前に用意してあるメモリ領域に」
「変なコトにならないように自動で」
「placement newする」
みたいなものっぽい。
で、パフォーマンス的にどんなもんじゃろ? という事で実測してみた。


10万回newして、毎回どのくらい時間かかっているかを出力。
コードがアレだけど見逃して…

#include <iostream>
#include <memory>
#include <list>
#include <boost/lexical_cast.hpp>
#include <boost/pool/object_pool.hpp>


#include <boost/chrono/system_clocks.hpp>

typedef boost::chrono::high_resolution_clock clock_type;

class Hoge{
public:
  Hoge(std::string name)
  {
    static int i=0; i++;
    name_ = name + boost::lexical_cast<std::string>(i);
  }
  void print()
  {
    std::cout << name_ << std::endl;
  }
  ~Hoge()
  {
//    std::cout << name_ << " deleted" << std::endl;
  }
private:
  std::string name_;
  char tmp[1024];
};

#include <fstream>



int main()
{
  std::ofstream ofs("test.csv", std::ios::app | std::ios::ate);
  for(int s=0; s < 1; ++s){
    boost::object_pool<Hoge> mem_pool(100000);

    {
      // Hogeを格納するlist
      std::list<std::shared_ptr<Hoge>> hoge_list;

      for(int i=0; i < 100000; ++i){
        clock_type::time_point start = clock_type::now();
#ifndef _
        // new (pool)
        Hoge* tmp = new Hoge("taro");

        // shared_ptrにくるむ
        auto object = std::shared_ptr<Hoge>( tmp);
#else
        // new (pool)
        Hoge* tmp = mem_pool.construct("taro");

        // shared_ptrにくるむ
        auto object = std::shared_ptr<Hoge>( tmp,
          // 通常のdeleteはできないので、デリータで解放するようにしておく
          // ラムダ式にはmem_poolの参照をバインドしておく
          [&mem_pool](Hoge* hoge)
        {
          mem_pool.destroy(hoge);
        }
        );
#endif

        // push_back
        hoge_list.push_back( object );
        boost::chrono::nanoseconds elapsed = clock_type::now() - start;
        ofs << i << "," << elapsed.count() << "," << std::endl;
      }
    }// hoge_list内のオブジェクト全てのデストラクタが走る
  }

  std::cout << "END" << std::endl;
  return 0;
}

結果は一番下に載せておく。
結果から見ると、メモリ領域の確保はstd::vectorと同じく「足りなくなったら既存領域のn倍を再確保」しているっぽい。MSVCでやったので、nは1.5。gccなら2。
ナマでnewするのに比べて、newにかかる時間の振れ幅はかなり抑えられているようだ。
最初の確保数を「1」とかの小さな値にすると、再確保が起きる度にガクッと処理が遅くなるので、これが所謂「時折ガクッとなるからゲームには使えない」という噂のモトっぽい。


結論としては、
「newするたびにカクッとなられるのは困るんだけど、自分でアロケータ書くの嫌だああああ! という時には使えるんじゃないですかね?」
という感じでした。



平均して凄い時間かかってるように見えるが、横は10万要素あるので「n個に1個の特別時間かかったの」が見えているだけ。逆に言えば、見えてない下のは「n個に1個」の頻度が少ないことがわかる。

最初に全部領域を確保しているせいか、最初だけアホみたいに重い。

やってはいけない典型例。無駄な再確保が行われている。あと1個要素を追加すれば、再確保が起きて一個上の完全劣化。