Nクイーン【後編】
後編では、ビット演算・マルチスレッドを用いた高速化について解説する。
ビット演算(ビットマップ)による高速化
前章では配列の配置フラグを使用することで高速化を達成したが、本章ではビット演算によりさらに高速化してみよう。 最近のCPUは以前(2000年代前半まで)ほどはクロックが向上せず、マルチコア・マルチスレッド化が進んでいる。 C++11からマルチスレッドのためのクラスが追加されたので、それを利用して高速化を図る。 async(launch::async, 関数名, 引数列…) でスレッドを生成し、std::future:get() で指定した関数の返り値を受け取ることができる。 スレッドはCPUのスレッド数上限よりかなり多く(数千?)起動できるが、 N個のスレッドを起動し、解数を合計するコードは以下のように記述できる。 マルチスレッドを使用した場合の処理時間計測結果を以下に示す。 処理時間測定結果:(Core i7-6700 3GHz, 32GB, Windows 10) 各種アルゴリズムを用いた8クイーン問題を解くプログラムを解説し、筆者の環境で17×17まで処理時間計測を行った。 ・2004年、電気通信大学の研究グループが、処理を並列化し、N=24の解の個数を世界で初めて発見。 ・2009年~10年、萩野谷一二氏(大阪府立大学)の部分解から全体解を構成するというアプローチで従来より10倍の高速化に成功 ・萩野谷氏(大阪府立大学)はGPUを用いた高速化も研究している ・27×27の世界記録を更新「Solving the N-Queens Puzzle for 27 Queens using FPGAs」
int cols の各ビットが、各カラムにクイーンを配置可能かどうかを表すものとする。
最初は全てのカラムに配置可能なので、初期値は全ビットが立っている (1<
N
解の数
BF(ミリ秒)
BT(ミリ秒)
BF(ミリ秒)
Bit(ミリ秒)
4
2
0
0
0
0
5
10
0
0
0
0
6
4
0
0
0
0
7
40
6
0
0
0
8
92
90
0
0
0
9
352
2112
1
1
0
10
724
N/A
5
8
0
11
2680
N/A
21
23
1
12
14200
N/A
113
50
7
13
73712
N/A
671
226
31
14
365596
N/A
4158
1361
179
15
2279184
N/A
N/A
N/A
1128
16
14772512
N/A
N/A
N/A
7496
マルチスレッドによる高速化
これは、微細化の限界、リーク電流・放熱問題などが原因でクロック向上が困難になったからだ。
コア・スレッド数が増えたのだから、それを活用して高速化を計るのが最近のトレンドになっている。
「マルチスレッド」とは複数の処理を直列ではなく並行して行うことだ。
N個の処理を並行して行うことができれば、理想的にはN倍の速度になる(下図参照)。
実際にはマルチスレッドのオーバーヘッドがあり、各処理が同時に終わるわけではないので、正確にN倍にはならない。
が、1分を要していた処理が6倍の10秒に短縮されれば体感的は大差になる。
ビット演算のときの backtrackingBitmap() を少しだけ改造し、解の個数を返すようにする。
int backtrackingBitmapMT(int cols, // 配置可能カラム位置
int lt, int rt, // 1 for 使用済み
int n, int NQ) // n:配置済みクイーン数, [0, NQ)
{
int count = 0;
int flags = ~(lt|rt) & cols;
while( flags ) {
int bit = -flags & flags; // 一番右の1のビットを取り出す
if( n + 1 == NQ )
++count; // 解を発見
else
count += backtrackingBitmapMT(cols^bit, (lt|bit)<<1, (rt|bit)>>1, n+1, NQ);
flags ^= bit; // 処理済みビットをOFF
}
return count;
}
盤面サイズ N の分だけスレッドを生成し、スレッドを vector
※ 本稿ではasync(), get() を使用したが、関数が値を返さない場合は thread(), join() を使用する。
c番目のスレッドは最初の行のcカラム目にクイーンを置いた場合の解の個数を計測する。
std::future はスレッドを保持するクラスで、テンプレート引数(この場合は int)で、スレッド関数が返す値を指定する。
物理的に並行処理されるのはスレッド数上限までなの、あまり多くしても意味がない。
Nクイーン問題の場合は、せいぜい数10なので、N個のスレッドを起動して問題ない。
#include <future>
.....
vector<std::future<int>> lst;
for (int bit = 1<<nq; (bit>>=1) != 0; ) {
int cols = (1<<nq)-1; // 配置可能カラム位置
int lt = 0, rt = 0; // 1 for 使用済み
// スレッド起動
lst.push_back(std::async(std::launch::async, backtrackingBitmapMT,
cols^bit, (lt|bit)<<1, (rt|bit)>>1, 1, nq));
}
int count = 0;
for(auto& f: lst)
count += f.get(); // スレッド修了を待ち、解回数を合計
処理時間計測を行った環境は、Core i7-6700で8コア・8スレッドで、約7倍高速になった。
マルチスレッドのオーバーヘッドがあるため、8コアで実行しても8倍の速度にはならないが、7倍の速度向上は体感的に大きい。
16コアのCPUであれば10倍以上になるのかもしれない、誰か実験してほしい。
N
解の数
BF(ミリ秒)
BT(ミリ秒)
BF(ミリ秒)
Bit(ミリ秒)
MT(ミリ秒)
4
2
0
0
0
0
0
5
10
0
0
0
0
0
6
4
0
0
0
0
0
7
40
6
0
0
0
0
8
92
90
0
0
0
0
9
352
2112
1
1
0
0
10
724
N/A
5
8
0
0
11
2680
N/A
21
23
1
0
12
14200
N/A
113
50
7
1
13
73712
N/A
671
226
31
5
14
365596
N/A
4158
1361
179
29
15
2279184
N/A
N/A
N/A
1128
190
16
14772512
N/A
N/A
N/A
7496
1161
17
95815104
N/A
N/A
N/A
N/A
7427
おわりに
本稿で解説したアルゴリズムは比較的簡単なもので、
より大きなサイズの問題を解くにはより高度なアルゴリズム、膨大な計算機資源が必要だ。
興味ある人は以下の参考文献を参照してぜひ追試してみてほしい。参考文献
参照:「Nクイーン問題(解の個数を求める)」 http://www.ic-net.or.jp/home/takaken/nt/queen/
参照:「N-queens Homepage in Japanese」 http://www.arch.cs.titech.ac.jp/~kise/nq/index.htm
参照:「NQueens問題」 http://deepgreen.game.coocan.jp/NQueens/nqueen_index.htm
参照:「対称解の特性を用いた N-Queens 問題の求解とGPU による高速化」(情報処理学会研究報告2012/03)
http://deepgreen.game.coocan.jp/NQueens(GPU)/GI27-07.pdf
参照:https://forums.xilinx.com/t5/Xcell-Daily-Blog-Archived/Solving-the-N-Queens-Puzzle-for-27-Queens-using-FPGAs/ba-p/692248
関連記事
投稿が見つかりません。
筆者:津田伸秀
プロフィール:テニス・オセロ・ボードゲーム・パズル類が趣味の年齢不詳のおじさん。 自宅研究員(主席)。vi と C++が好き。迷走中・・・ ボードゲーム・パズル系アプリ開発・リリースしてます。