非同期アレコレ

[前回の記事](http://qiita.com/threecups/items/5bfeb535927b16000e70 "CRITICAL_SECTION用のマクロ")の続きというか、補足というか、蛇足というか。

#あれから
結論として`std::mutex`使おう、と勝手に放り投げたのですが
速度的にメチャクチャ遅くて、ちょっと使い物にならないなぁという結果になったので
コメント頂いた内容など含めて、速度を検証してみました
単純な排他制御コストの比較なので、他のところで早くしろっていうツッコミは無しよ？
（例：`OpenMP`のリダクション指示節とか）

**※追記：`std::mutex`が異常に遅いというのは、VisualStudio2013の問題だったようです。**

#テストプログラム
##概要
int型のグローバル変数に加算処理をして、最終的な変数値を確認するという
色気も何もあったもんじゃないテストを実行して、処理時間と演算結果を確認します
排他制御については、前回の記事のコメントを参考にさせて頂いてます。

##環境
Windows 7 (64bit) 上で確認
開発環境はVisualStudio2013
ターゲットはコンソール（x86）リリースビルド
CPUは4C8T
スレッド数は16、各スレッド内の処理回数は100,000回（この辺りはソースコード参照）

##手法０
とりあえず基本ということで、排他制御が無い場合の時間と結果を確認するための関数を用意

```func_00.cpp
void func_00()
{
	g_counter++;
}
```

##手法１
古来より伝えられし手法（`CRITICAL_SECTION`を素直に使用）

```func_01.cpp
void func_01()
{
	EnterCriticalSection(&g_cs);

	g_counter++;

	LeaveCriticalSection(&g_cs);
}
```

##手法２
std::mutexを直接叩く手法

```func_02.cpp
void func_02()
{
	g_mtx.lock();

	g_counter++;

	g_mtx.unlock();
}
```

##手法３
前回の記事で記載したマクロを使用する手法
本質的にはCRITICAL_SECTIONを使うので、手法１と同じ

```func_03.cpp
#define lock_03(x) for(char _z = (EnterCriticalSection(&x), 1); _z; _z = (LeaveCriticalSection(&x), 0))

void func_03()
{
	lock_03(g_cs)
	{
		g_counter++;
	}
}
```

##手法４
前回の記事でコメント頂いた手法
C#のlock記述とは少し変わるが、ラムダ式に慣れている人なら素直に読めるはず
途中でreturnしても`func_04`からは抜けないという点において、C#のlockとは挙動が異なりますが
デッドロックする手法１よりは遥かにマシでしょう

```func_04.cpp
template<typename F>
void lock_04(CRITICAL_SECTION& cs, F&& f)
{
	EnterCriticalSection(&cs);
	f();
	LeaveCriticalSection(&cs);
}

void func_04()
{
	lock_04(g_cs, []()
	{
		g_counter++;
	});
}
```

##手法５
前回の記事でコメント頂いた手法
critical_section オブジェクトの例外セーフ RAII ラッパーを使った方式、らしい

```func_05.cpp
void func_05()
{
	concurrency::critical_section::scoped_lock lock(g_cs_2);

	g_counter++;
}
```

##手法６
前回の記事でコメント頂いた手法
std::lock_guardを使用した方式

```func_06.cpp
void func_06()
{
	std::lock_guard<decltype(g_mtx)> lock(g_mtx);

	g_counter++;
}
```

##起動部
これらを呼び出すmainプログラムとglobal変数たち
実際には`func_00`から`func_06`も同じソースコードに記述してますが

```main.cpp
#include <cstdio>
#include <mutex>

#include <Windows.h>
#include <process.h>
#include <concrt.h>

static const int THREAD_NUM =     16;
static const int   LOOP_NUM = 100000;

static int g_counter;

static CRITICAL_SECTION g_cs;
static std::mutex g_mtx;
static concurrency::critical_section g_cs_2;

unsigned int __stdcall invoker(void * param)
{
	void(* func)() = (void(*)())param;

	for (int i = 0; i < LOOP_NUM; i++)
		func();

	return 0;
}

void exec_test(void(* func)(), const char * test_name)
{
	LARGE_INTEGER Clock, Time0, Time1;
	HANDLE hThread[THREAD_NUM];
	INT Time_ms;

	/* 開始時間取得 */
	QueryPerformanceFrequency(&Clock);
	QueryPerformanceCounter  (&Time0);

	/* 変数初期化 */
	g_counter = 0;

	/* スレッド開始 */
	for (int i = 0; i < _countof(hThread); i++)
		hThread[i] = (HANDLE)_beginthreadex(NULL, 0, invoker, func, 0, NULL);

	/* スレッド待機 */
	WaitForMultipleObjects(_countof(hThread), hThread, TRUE, INFINITE);

	/* スレッド終了 */
	for (int i = 0; i < _countof(hThread); i++)
		CloseHandle(hThread[i]);

	/* 終了時間取得 */
	QueryPerformanceCounter  (&Time1);

	/* 処理時間をミリ秒に変換 */
	Time_ms = (1000 * (Time1.QuadPart - Time0.QuadPart) / Clock.QuadPart);

	/* メッセージ出力 */
	printf("%s : %6d[ms] ---- counter = %7d / %7d\n", test_name, Time_ms, g_counter, LOOP_NUM * THREAD_NUM);
}

int main(int argc, int * argv[])
{
	InitializeCriticalSection(&g_cs);

	timeBeginPeriod(1);

	exec_test(func_00, "test_00");
	exec_test(func_01, "test_01");
	exec_test(func_02, "test_02");
	exec_test(func_03, "test_03");
	exec_test(func_04, "test_04");
	exec_test(func_05, "test_05");
	exec_test(func_06, "test_06");

	timeEndPeriod  (1);

	DeleteCriticalSection(&g_cs);
}
```

#処理結果
前置きが長くなりましたが、処理結果はコチラ

```
test_00 :      3[ms] ---- counter =  649817 / 1600000
test_01 :     52[ms] ---- counter = 1600000 / 1600000
test_02 :  14951[ms] ---- counter = 1600000 / 1600000
test_03 :     53[ms] ---- counter = 1600000 / 1600000
test_04 :     53[ms] ---- counter = 1600000 / 1600000
test_05 :  16136[ms] ---- counter = 1600000 / 1600000
test_06 :  17098[ms] ---- counter = 1600000 / 1600000
```

まず手法０については積算値が合ってないので論外ですが、トータル3msです。これが処理部だけ考えた時のCPU実力

手法１、３、４は全てCRITICAL_SECTIONを使った方式ですが、処理時間はほぼ同じです。
もうちょっとオーバーヘッドの影響でるかと思いましたが、コンパイラが賢いのかな？

手法２、５、６は他に比べて明らかに遅いです、遅すぎます。
手法２、６は`std::mutex`を使用しているので処理速度が違うのは予測が付くのですが
手法５は`critical_section`という名前の割に……内部では`std::mutex`を使ってるような気がしますね
これ、`std::mutex`を差し置いて採用する利点あるんかしら……？

#最後に
やっぱり勝手にまとめますが

- 移植性を高めたいなら`std::mutex`を使おう
- 中でもプログラムの堅牢性を高めたいなら、RAII原則に従ったライブラリ`std::lock_guard`等を使おう
- どうしても速度が欲しいならOSのAPIを叩こう、リソース管理には細心の注意を

以上、お粗末。