読者です 読者をやめる 読者になる 読者になる

底辺大学の院生がプログラミングや機械学習を勉強するブログ

勉強していることを雑にまとめるブログ。当然、正しさの保証は一切しない。

円周率を求めるシミュレータをマルチスレッド化して高速化する

C++11を使ったマルチスレッドプログラミング。

モンテカルロ法で円周率を求める。
モンテカルロ法 - Wikipedia


一番簡単に非同期処理ができるstd::threadで実行してもいいけど、こちらは戻り値を返せない。
一応、結果を入れたい変数を参照渡しするとか、結果をグローバル変数に代入するとか色々手はあるけど、変数へのアクセスが他スレッドと競合しないよう排他制御を考慮する必要があるので非常に面倒。

そこで戻り値を返すことができる非同期実行であるstd::asyncを使用する。

std::future<int> f = std::async(std::launch::async, func);

結果はgetメソッドで取得できる。

const int result = f.get();

また、threadもasyncもSTLコンテナに入れることができるので、同じ処理を複数のスレッドで実行したい、みたいなときは非常に便利。

std::vector<std::future<int>> v;
v.push_back(std::async(std::launch::async, func));

関数部はラムダ式でもいいし、もちろんキャプチャもできる。

int a = 1;
std::vector<std::future<int>> v;
v.push_back(std::async(std::launch::async, [a]{ return a; }));

というわけでこれらを駆使してコードを書く。

#include <iostream>
#include <future>
#include <random>

#define _USE_MATH_DEFINES
#include <math.h>


int main(int argc, char** argv)
{
	// 引数が不足していればエラー
	if (argc != 3)
	{
		printf("$N_thread $N_trial\n");
		return EXIT_FAILURE;
	}

	// スレッド数
	const int N_thread = std::atoi(argv[1]);
	printf("# N_thread\t= %d\n", N_thread);

	// 試行回数
	const long N_trial = std::atol(argv[2]);
	printf("# N_trial\t= %d\n", N_trial);


	// スレッドの実行
	std::vector<std::future<long>> v;
	for (int t = 0; t < N_thread; t++)
	{
		const long N_trial_per_thread = t == 0 ? N_trial / N_thread + N_trial % N_thread : N_trial / N_thread;
		v.push_back(std::async(std::launch::async, [N_trial_per_thread]
		{
			// 乱数生成器
			std::random_device rd;
			// メルセンヌ・ツイスタ
			std::mt19937 mt(rd());
			// [0:1]の一様分布
			std::uniform_real_distribution<double> ud(0.0, 1.0);

			long cnt = 0;
			for (long t = 0; t < N_trial_per_thread; t++)
			{
				cnt += std::pow(ud(mt), 2.0) + std::pow(ud(mt), 2.0) < 1.0 ? 1 : 0;
			}
			return cnt;
		}));
	}

	// 各スレッドの結果を集計
	long cnt_sum = 0;
	for (int t = 0; t < N_thread; t++)
	{
		cnt_sum += v[t].get();
	}


	// 結果を印字
	printf("# \n");
	printf("# M_PI\t\tMonteCarlo\n");
	printf("%.10f\t%.10f\n", M_PI, 4.0 * cnt_sum / N_trial);
	
	return EXIT_SUCCESS;
}

スレッド数4、試行回数100,000,000回で実行した。

f:id:telltales:20160702050050p:plain

小数第3位までしか合わないのかー、なかなか精度が低い。


ちなみにうちのノート君は論理コアが4つしか無いので4スレッドで実行するとCPU使用率が100%になる。

f:id:telltales:20160702050045j:plain

他のことをしたいのであれば、コア数-1ぐらいにスレッド数を設定するといい感じだった。



参考にしたサイト。

qiita.com

非同期処理について分かりやすくまとめられている。