Hatena::ブログ(Diary)

joynote break;

2014-01-27

[][] Boostビルドオプション

毎度アレなので、健忘録的メモ

b2.exe install toolset=msvc-10.0 link=static runtime-link=static variant=debug,release threading=multi

2013-11-18

[][] Boost.Coroutine

Boost.CoroutineがBoost 1.55で新しいインターフェイスを搭載したとの事なので、何をどう書けばどう動くかを書いてみます。

pull_type/push_type?

今までのBoost.Coroutineは何か良く分からないシグネチャを持ったcoroutine関連らしきクラスを引数として、それを双方向にぐにゃぐにゃやる必要があって非常に面倒でした。

しかし流石に、作者の人も使いにくいのが分かっていたらしく、今回大幅に整理してくれたようです。

まず、双方向なアクセスが出来たクラス(caller_type)を廃止し、値を外に返す側(push_type)と値を取ってくる側(pull_type)の片方向x2に変更されました。

基本的には、

// 値を渡す側
boost::coroutines::coroutine<渡したい値の型>::push_type

// 値を受け取る側
boost::coroutines::coroutine<受け取りたい値の型>::pull_type

// ※当然ながら、「渡したい値の型==受け取りたい値の型」であること。

の2つを対にして使う、と覚えておけばいいと思います。

それでは、値を受け取る方向のサンプルコードと、値を渡す方向のサンプルコードを書いてみます。

ジェネレータ

まず、Coroutineの一般的な用途でよく使われそうな【値を1個ずつ生成する関数】(値を受け取る方向)を書く方法から。

いわゆる「ジェネレータ」という奴で、必要になる度に1個ずつ値を取ってきたい時に使う奴ですね。

#include <iostream>
#include <boost/coroutine/all.hpp>

using namespace std;

// 実行する関数
void Action(boost::coroutines::coroutine<int>::push_type& yield)
{
	// 1回呼ばれる毎に0,1,2,3,4 と値を1つずつ返す
	for (int i = 0; i < 5; i++)
	{
		yield(i);
	}
}

// range-based for
void Sample1()
{
	boost::coroutines::coroutine<int>::pull_type co(Action);

	cout << "start loop." << endl;

	for (auto& item : co)
	{
		cout << item << endl;
	}
}

// イテレータアクセス
void Sample2()
{
	boost::coroutines::coroutine<int>::pull_type co(Action);

	cout << "start loop." << endl;

	for (auto it = boost::begin(co); it != boost::end(co); ++it)
	{
		cout << (*it) << endl;
	}
}

// 値取れるかチェック->get->次のyield
void Sample3()
{
	boost::coroutines::coroutine<int>::pull_type co(Action);

	cout << "start loop." << endl;
	
	while (co.has_result())
	{
		cout << co.get() << endl;
		co();
	}
}

int main()
{
	cout << "- - - - - - - " << endl;
	Sample1();
	cout << "- - - - - - - " << endl;
	Sample2();
	cout << "- - - - - - - " << endl;
	Sample3();
	cout << "- - - - - - - " << endl;
	return 0;
}

出力結果:

- - - - - - -
start loop.
0
1
2
3
4
- - - - - - -
start loop.
0
1
2
3
4
- - - - - - -
start loop.
0
1
2
3
4
- - - - - - -

数値を渡していくやつ

次は、(こういうアレの名前知らないんですが…)値を渡していく奴を書いてみます。

処理としては、値を1つずつ関数に渡していって、関数は受け取った値と、今までの合計を標準出力に吐き出し続ける感じ(値を渡す方向)です。

先ほどのコードとは、pull_typeとpush_typeの位置が逆転してる事が分かります。

#include <iostream>
#include <boost/coroutine/all.hpp>

using namespace std;

// 実行する関数1
void Sample1(boost::coroutines::coroutine<int>::pull_type& co)
{
	int total = 0;
	for(auto& item : co)
	{
		cout << item << endl;
		total += item;
		cout << "total:" << total << endl;
	}
}

// 実行する関数2
void Sample2(boost::coroutines::coroutine<int>::pull_type& co)
{
	int total = 0;
	for (auto it = boost::begin(co); it != boost::end(co); ++it)
	{
		cout << (*it) << endl;
		total += (*it);
		cout << "total:" << total << endl;
	}
}

// 実行する関数3
void Sample3(boost::coroutines::coroutine<int>::pull_type& co)
{
	int total = 0;
	while (co.has_result()){
		cout << co.get() << endl;
		total += co.get();
		cout << "total:" << total << endl;
		co();
	}
}

int main()
{
	cout << "- - - - - - - " << endl;
	boost::coroutines::coroutine<int>::push_type yield1(Sample1);

	cout << "start loop." << endl;

	for (int i = 1; i <= 5; i++)
	{
		yield1(i);
	}
	
	cout << "- - - - - - - " << endl;
	boost::coroutines::coroutine<int>::push_type yield2(Sample2);

	cout << "start loop." << endl;

	for (int i = 1; i <= 5; i++)
	{
		yield2(i);
	}

	cout << "- - - - - - - " << endl;
	boost::coroutines::coroutine<int>::push_type yield3(Sample3);

	cout << "start loop." << endl;

	for (int i = 1; i <= 5; i++)
	{
		yield3(i);
	}

	return 0;
}

出力結果:

- - - - - - -
start loop.
1
total:1
2
total:3
3
total:6
4
total:10
5
total:15
- - - - - - -
start loop.
1
total:1
2
total:3
3
total:6
4
total:10
5
total:15
- - - - - - -
start loop.
1
total:1
2
total:3
3
total:6
4
total:10
5
total:15

実に対照的なので分かりやすい。

動的ゲームでよく使いたい奴

そして(個人的には)本題の、動的ゲームでよく使うような使い方。

DXライブラリ導入してコピペで動くヤツが以下。

#include "DxLib.h"
#include <boost/coroutine/all.hpp>


class Actor{
public:
	Actor() : co(Action)
	{}

	void Update()
	{
		// カウントが0以下の時、Actionを実行
		if (--count <= 0)
		{
			co(this);
		}
	}

	void Draw()
	{
		DrawCircle(x, y, 30, GetColor(0, 0, 255), TRUE);
	}
private:
	static void Action(boost::coroutines::coroutine<Actor*>::pull_type& yield)
	{
		while (true)
		{
			// x方向に5秒使って100px進む
			for (size_t i = 0; i < 5 * 60; i++)
			{
				yield.get()->x++;
				yield();
			}
			
			// 1秒止まる
			yield.get()->count = 60;
			yield();

			// y方向に5秒使って100px進む
			for (size_t i = 0; i < 5 * 60; i++)
			{
				yield.get()->y++;
				yield();
			}

			// 1秒止まる
			yield.get()->count = 60;
			yield();

			// x方向に5秒使って100px戻る
			for (size_t i = 0; i < 5 * 60; i++)
			{
				yield.get()->x--;
				yield();
			}

			// 1秒止まる
			yield.get()->count = 60;
			yield();

			// y方向に5秒使って100px戻る
			for (size_t i = 0; i < 5 * 60; i++)
			{
				yield.get()->y--;
				yield();
			}

			// 1秒止まる
			yield.get()->count = 60;
			yield();
		}
	}
private:
	int count = 0;
	int x = 100;
	int y = 100;
	boost::coroutines::coroutine<Actor*>::push_type co;
};


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	LPSTR lpCmdLine, int nCmdShow)
{
	// ウインドウモードに変更
	ChangeWindowMode(TRUE);

	if (DxLib_Init() == -1)        // DXライブラリ初期化処理
	{
		return -1;        // エラーが起きたら直ちに終了
	}
	SetDrawScreen(DX_SCREEN_BACK);

	// Actor実体化
	Actor actor;

	// Zキーの入力待ち
	while (CheckHitKey(KEY_INPUT_Z) == 0 && ProcessMessage() != -1)
	{
		ClearDrawScreen();

		// □な感じに移動し続ける
		actor.Update();
		actor.Draw();

		ScreenFlip();
	}

	DxLib_End();            // DXライブラリ使用の終了処理

	return 0;            // ソフトの終了
}

"yield.get()->"が凄く汚くて嫌なので、たぶん実際にゲーム用のコード書くときはもっとマシな方法模索すると思いますが…。


まあ、そんな感じで一応動くんじゃないでしょうか!

2013-10-29

[] あまりにも更新していない

このままだとブログに埃が積もってしまうので、C++に限らず月一くらいで何か書いていきたい(戒め)

2013-04-06

[][] Boost.Coroutineの速度

前略。

http://d.hatena.ne.jp/joynote/20121207/1354863235

この記事のコードを改造して、生でカウンタとif文で動きを制御するコードを追加。

コルーチンを使った場合と、煩雑になるが生で制御した場合の速度差を実測した。

検証コードが厳密じゃない(タイマーの実行コスト等の考慮足りない)のでコードは割愛するが、

(あとでちゃんとした検証と共に載せたい)

同じ動作を完了するのに、

・1.41倍程度コルーチンの方が時間がかかる

という結果が出た。

1.41倍なら流石にLuaJITでもXtalでも完全に追いつけない速度なので、スクリプト言語で動作を記述して、遅いようならC++生で書くという作戦が上手くいきそうな手応えを感じた。

というメモ。

2012-12-23

[] 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するたびにカクッとなられるのは困るんだけど、自分でアロケータ書くの嫌だああああ! という時には使えるんじゃないですかね?」

という感じでした。


f:id:joynote:20121223164426j:image

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

f:id:joynote:20121223164427j:image

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

f:id:joynote:20121223164428j:image

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