BonanzaのMPI並列化 (2014/11/24版)

Bonanza のソースコードをMPI対応に書き換えて並列実行する方法について説明します。
Bonanzaのバージョンは6.0とします。
OSはWindowsとします。

1. 開発環境の準備
2. BonanzaのMPI化
3. masterプログラムの作成
4. コンパイル・リンク
5. 実行方法

1. 開発環境の準備

コンパイラとして"Microsoft Visual C++"がインストールされているものとします。
下記などからダウンロードすることができます。
Visual Studio Community 2013

MPIの開発環境は下記からダウンロードすることができます。
Microsoft MPI v5
以下の2つをダウンロードして実行して下さい。

  1. msmpisdk.msi : MPI開発環境
  2. MSMpiSetup.exe : MPI実行環境

インストールが終了すると以下のファイルがコピーされています。()内はフォルダです。

  1. mpi.h (C:\Program Files (x86)\Microsoft SDKs\MPI\Include)
    MPI用のヘッダーファイルです。MPI関数を使用するソースコードでincludeします。
  2. msmpi.lib (C:\Program Files (x86)\Microsoft SDKs\MPI\Lib\x64)
    MPIのライブラリーです。リンク時に指定します。
  3. msmpi.dll (C:\Windows\System32)
    MPIプログラムが実行時に使用するDLLです。
  4. mpiexec.exe (C:\Program Files\Microsoft MPI\Bin)
    MPIプログラムのランチャーです。 GUIがこれを呼び出しますので実行プログラムのあるパスに複製をコピーして下さい。
  5. smpd.exe (C:\Program Files\Microsoft MPI\Bin)
    MPI用デーモンです。複数ノードで並列計算するときコマンドラインで実行します。

環境変数を以下のように設定して下さい。

2. BonanzaのMPI化

以下のファイルはBonanzaのソースコードを修正したものです。
(1) main.c
(2) io.c
以下のファイルは新規に作成したものです。
(3) mympi.h : ヘッダーファイル
(4) master_v0.c : 1masterと1workerで動く最小プログラム
(5) master_v1.c : 1masterと複数workerで動く並列プログラムの簡単な例
その他
(6) Makefile : VC++用のMakefile

Bonanzaのソースコード(src/client)をすべて作業用フォルダにコピーし、 上記のファイルをダウンロードして展開し上書きコピーして下さい。
なお、(1)(2)は修正個所が少ないので直接編集しても構いません。

Bonanzaのソースコードを以下のように変更します。
MPIプログラムではコンパイルオプションでマクロ"MPI"が定義されています。
ソースコードの赤い部分が追加または編集したところです。削除したところはありません。

(1) main.c
     1	#include <stdio.h>
     2	#include <stdlib.h>
     3	#include <string.h>
     4	#if defined(_WIN32)
     5	#  include <fcntl.h>
     6	#endif
     7	#include "shogi.h"
     8	
     9	#if defined(MPI)
    10	#  include <mpi.h>
    11	#  include "mympi.h"
    12	#endif
    13	
    14	static int main_child( tree_t * restrict ptree );
    15	extern void master( int, char ** );
    16	
    17	int
    18	#if defined(CSASHOGI) || defined(USI) || defined(MPI)
    19	main( int argc, char *argv[] )
    20	#else
    21	main()
    22	#endif
    23	{
    24	  int iret;
    25	  tree_t * restrict ptree;
    26	
    27	// initialize MPI and start master process
    28	#if defined(MPI)
    29	  MPI_Init( &argc, &argv );
    30	  MPI_Comm_size( MPI_COMM_WORLD, &commSize );
    31	  MPI_Comm_rank( MPI_COMM_WORLD, &commRank );
    32	
    33	  if (commRank == 0) {
    34	    master( argc, argv );
    35	  }
    36	  else {
    37	#endif
    38	
    39	#if defined(TLP)
    40	  ptree = tlp_atree_work;
    41	#else
    42	  ptree = &tree;
    43	#endif
    44	/*
    45	#if defined(CSASHOGI) && defined(_WIN32)
    46	  if ( argc != 2 || strcmp( argv[1], "csa_shogi" ) )
    47	    {
    48	      MessageBox( NULL,
    49			  "The executable image is not intended\x0d"
    50			  "as an independent program file.\x0d"
    51			  "Execute CSA.EXE instead.",
    52			  str_myname, MB_OK | MB_ICONINFORMATION );
    53	      return EXIT_FAILURE;
    54	    }
    55	#endif
    56	*/
(略)
    86	  if ( fin() < 0 ) { out_error( "%s", str_error ); }
    87	
    88	// close MPI
    89	#if defined(MPI)
    90	  }
    91	  MPI_Finalize();
    92	#endif
    93	
    94	  return EXIT_SUCCESS;
    95	}
(略)

(2) io.c
(略)
    16	#if defined(MPI)
    17	#  include <mpi.h>
    18	#  include "mympi.h"
    19	#endif
(略)
    99	#if defined(CSASHOGI)
   100	void
   101	out_csashogi( const char *format, ... )
   102	{
   103	#if defined(MPI)
   104	  char send[COMMSIZ];
   105	  va_list arg;
   106	
   107	  va_start( arg, format );
   108	  vsprintf( send, format, arg );
   109	  va_end( arg );
   110	
   111	  MPI_Send( send, COMMSIZ, MPI_CHAR, 0, 0, MPI_COMM_WORLD );
   112	#else
   113	  va_list arg;
   114	
   115	  va_start( arg, format );
   116	  vprintf( format, arg );
   117	  va_end( arg );
   118	
   119	  fflush( stdout );
   120	#endif
   121	}
   122	#endif
(略)
   649	static int CONV
   650	read_command( char ** pstr_line_end )
   651	{
   652	  char *str_end;
   653	  int count_byte, count_cmdbuff;
   654	#if defined(MPI)
   655	  char recv[COMMSIZ];
   656	  MPI_Status status;
   657	#endif
(略)
   689	#if defined(MPI)
   690	  MPI_Recv( recv, COMMSIZ, MPI_CHAR, 0, 0, MPI_COMM_WORLD, &status );
   691	  strcpy( str_end, recv );
   692	  count_byte = (int)strlen( str_end );
   693	#else
   694	  do { count_byte = (int)read( 0, str_end, SIZE_CMDBUFFER-1-count_cmdbuff ); }
   695	  while ( count_byte < 0 && errno == EINTR );
   696	  
   697	  if ( count_byte < 0 )
   698	    {
   699	      str_error = "read() faild.";
   700	      return -1;
   701	    }
   702	  *( str_end + count_byte ) = '\0';
   703	#endif
(略)

(3) mympi.h
     1	#ifndef _MYMPI_H_
     2	#define _MYMPI_H_
     3	
     4	#define COMMSIZ 512
     5	
     6	int commSize, commRank;
     7	
     8	#endif  // _MYMPI_H_

3. masterプログラムの作成

前節の変更により、Bonanza思考部がworkerになりました。 次にworkerと作業のやり取りをするmasterを作成します。
ここでは以下の2つのプログラムを作成します。

(4) master_v0.c
     1	/*
     2	Bonanza MPI並列プログラム Version 0 : master_v0.c
     3	
     4	○実行方法
     5	$ mpiexec -n 2 bonanza_mpi_v0.exe
     6	
     7	○仕様
     8	(1)プロセス数=2
     9	(2)1個のmaster(プロセス番号=0)と1個のworker(プロセス番号=1)
    10	(3)masterがworkerと送受信する、データは加工せずそのまま送受信する
    11	(4)tagは使用しない(=0)
    12	(5)logファイル(master.log)は必須ではないがdebugに便利
    13	(先後の実行プログラムが同じパスにあるときは競合するので注意)
    14	*/
    15	
    16	#include <stdio.h>
    17	#include <string.h>
    18	#include <ctype.h>
    19	
    20	#if defined(MPI)
    21	#  include <mpi.h>
    22	#  include "mympi.h"
    23	#endif
    24	
    25	static int is_move( const char * );
    26	
    27	void master( int argc, char **argv )
    28	{
    29	#if defined(MPI)
    30		int flag;
    31		char strline[BUFSIZ], strbuff[2048];
    32		char send[COMMSIZ], recv[COMMSIZ];
    33		MPI_Status status;
    34		FILE *fp;
    35		int nmove = 1;   // 手数:必須ではないがlog出力に使用する
    36		const char fn[] = "master.log";
    37		const char sendfmt[] = "%03d >%03d %s";
    38		const char recvfmt[] = "%03d <%03d %s";
    39	
    40		if ( commSize != 2 ) MPI_Abort( MPI_COMM_WORLD, 0 );
    41	
    42		// 必要なら引数処理
    43		argc = argc;
    44		argv = argv;
    45	
    46		// logファイルを開く
    47		if ( ( fp = fopen( fn, "w" ) ) == NULL ) {
    48			fprintf( stderr, "file %s open error\n", fn );
    49			return;
    50		}
    51	
    52		// Bonanza header(1行)を受信する
    53		MPI_Recv( recv, COMMSIZ, MPI_CHAR, 1, 0, MPI_COMM_WORLD, &status );
    54		// 標準出力
    55		printf( "%s", recv ); fflush( stdout );
    56		// log
    57		fprintf( fp, recvfmt, 0, 1, recv ); fflush( fp );
    58	
    59		// 標準入力からコマンドを入力する
    60		while ( fgets( strline, BUFSIZ, stdin ) != NULL ) {
    61			// 文字列strlineの最後は\n
    62	
    63			// workerにそのまま送信する
    64			strcpy( send, strline );
    65			MPI_Send( send, COMMSIZ, MPI_CHAR, 1, 0, MPI_COMM_WORLD );
    66			// log
    67			fprintf( fp, sendfmt, nmove, 1, send ); fflush( fp );
    68	
    69			// 指し手のときは手数++("7776FU"または"move 7776FU")
    70			if ( is_move( strline ) ||
    71				( ! strncmp( strline, "move ", 5 ) && is_move( strline + 5 ) ) ) {
    72				nmove++;
    73			}
    74	
    75			// quit:master終了(workerにもquitが送られている)
    76			if ( ! strncmp( strline, "quit", 4 ) ) {
    77				break;
    78			}
    79	
    80			// 思考命令("7776FU"または"move")のとき("move 7776FU"は除く)
    81			if ( is_move( strline ) ||
    82				( ! strncmp( strline, "move", 4 ) && ( strline[4] != ' ' ) ) ) {
    83	
    84				// 受信ループ
    85				strcpy( strbuff, "" );
    86				flag = 1;
    87				while ( flag ) {
    88					// workerから受信
    89					MPI_Recv( recv, COMMSIZ, MPI_CHAR, 1, 0, MPI_COMM_WORLD, &status );
    90	
    91					// 文字列連結(info/stat行は複数回に分けて送られてくることがある)
    92					strcat( strbuff, recv );
    93	
    94					// recvが改行を含むとき
    95					if ( strchr( recv, '\n' ) ) {
    96						// 標準出力
    97						printf( "%s", strbuff ); fflush( stdout );
    98						// log
    99						fprintf( fp, recvfmt, nmove, 1, strbuff ); fflush( fp );
   100						// 初期化
   101						strcpy( strbuff, "" );
   102					}
   103	
   104					// recvが"info tt"で始まる:次の手に進む
   105					if ( ! strncmp( recv, "info tt", 7 ) ) {
   106						flag = 0;
   107					}
   108				}
   109	
   110				// 手数++
   111				nmove++;
   112			}
   113		}
   114	
   115		// logファイルを閉じる
   116		fclose( fp );
   117	#endif
   118	}
   119	
   120	// CSA形式指し手(7776FU)であるか
   121	static int is_move( const char *str )
   122	{
   123		return ( strlen( str ) > 5 )
   124			&& isdigit( (int)str[0] ) && isdigit( (int)str[1] )
   125			&& isdigit( (int)str[2] ) && isdigit( (int)str[3] )
   126			&& isupper( (int)str[4] ) && isupper( (int)str[5] );
   127	}

(5) master_v1.c
     1	/*
     2	Bonanza MPI並列プログラム Version 1 : master_v1.c
     3	
     4	○実行方法
     5	$ mpiexec -n <num> bonanza_mpi_v1.exe  (<num> >= 2)
     6	
     7	○仕様
     8	(1)プロセス数N>=2
     9	(2)1個のmaster(プロセス番号=0)と(N-1)個のworker(プロセス番号=1,...)
    10	(3)masterが各workerと送受信する、データは加工せずそのまま送受信する
    11	(4)評価値の最もよい指し手を選択する(楽観的合議)
    12	
    13	○既知のバグ
    14	・対局開始時に指定した[持ち時間]が無効になっている
    15	初手から秒読みにするか毎回思考時間を設定する必要がある
    16	・予測読みに対応していないのでponderはOFFにする
    17	*/
    18	
    19	#include <stdlib.h>
    20	#include <stdio.h>
    21	#include <string.h>
    22	#include <ctype.h>
    23	
    24	#if defined(MPI)
    25	#  include <mpi.h>
    26	#  include "mympi.h"
    27	#endif
    28	
    29	static int is_move( const char * );
    30	
    31	void master( int argc, char **argv )
    32	{
    33	#if defined(MPI)
    34		int i, n;
    35		int numdone, ibest, sbest;
    36		int *score;
    37		char strline[BUFSIZ], strkifu[1024][7];
    38		char send[COMMSIZ], recv[COMMSIZ];
    39		char **strbuff, **strinfo, **strstat, **strmove;
    40		MPI_Status status;
    41		FILE *fp;
    42		int nmove = 1;             // 手数(開始局面が1)
    43		const int bufsiz = 2048;   // info行の最大文字数
    44		const char fn[] = "master.log";
    45		const char sendfmt[] = "%03d >%03d %s";
    46		const char recvfmt[] = "%03d <%03d %s";
    47	
    48		if ( commSize < 2 ) MPI_Abort( MPI_COMM_WORLD, 0 );
    49	
    50		// 必要なら引数処理
    51		argc = argc;
    52		argv = argv;
    53	
    54		// alloc(探索結果を格納する配列)
    55		strbuff = (char **)malloc( commSize * sizeof( char * ) );
    56		strinfo = (char **)malloc( commSize * sizeof( char * ) );
    57		strstat = (char **)malloc( commSize * sizeof( char * ) );
    58		strmove = (char **)malloc( commSize * sizeof( char * ) );
    59		for ( i = 0; i < commSize; i++ ) {
    60			strbuff[i] = (char *)malloc( bufsiz * sizeof( char ) );
    61			strinfo[i] = (char *)malloc( bufsiz * sizeof( char ) );
    62			strstat[i] = (char *)malloc( bufsiz * sizeof( char ) );
    63			strmove[i] = (char *)malloc( bufsiz * sizeof( char ) );
    64		}
    65		score = (int *)malloc( commSize * sizeof( int ) );
    66	
    67		// logファイルを開く
    68		if ( ( fp = fopen( fn, "w" ) ) == NULL ) {
    69			fprintf( stderr, "file %s open error\n", fn );
    70			return;
    71		}
    72	
    73		// Bonanza header : 各workerから1行受信する
    74		for ( i = 1; i < commSize; i++ ) {
    75			// 受信
    76			MPI_Recv( recv, COMMSIZ, MPI_CHAR, i, 0, MPI_COMM_WORLD, &status );
    77			// 標準出力(worker#1のみでよい)
    78			if ( i == 1 ) { printf( "%s", recv ); fflush( stdout ); }
    79			// log
    80			fprintf( fp, recvfmt, 0, i, recv ); fflush( fp );
    81		}
    82	
    83		// 標準入力からコマンドを入力する
    84		while ( fgets( strline, BUFSIZ, stdin ) != NULL ) {
    85			// 文字列strlineの最後は\n
    86	/*
    87			// debug用、worker毎に違う思考条件を与えると擬似的に合議ができる
    88			for ( i = 1; i < commSize; i++ ) {
    89				sprintf( send, "limit time 0 %d\n", i );
    90				MPI_Send( send, COMMSIZ, MPI_CHAR, i, 0, MPI_COMM_WORLD );
    91				fprintf( fp, sendfmt, nmove, i, send ); fflush( fp );
    92			}
    93	*/
    94			// 全workerにそのまま送信する
    95			strcpy( send, strline );
    96			for ( i = 1; i < commSize; i++ ) {
    97				// 送信
    98				MPI_Send( send, COMMSIZ, MPI_CHAR, i, 0, MPI_COMM_WORLD );
    99				// log
   100				fprintf( fp, sendfmt, nmove, i, send ); fflush( fp );
   101			}
   102	
   103			// 指し手のとき("7776FU")
   104			if ( is_move( strline ) ) {
   105				strncpy( strkifu[nmove], strline, 6 );
   106				strkifu[nmove][6] = '\0';
   107				nmove++;
   108			}
   109			// 指し手のとき("move 7776FU")
   110			if ( ! strncmp( strline, "move ", 5 ) && is_move( strline + 5 ) ) {
   111				strncpy( strkifu[nmove], strline + 5, 6 );
   112				strkifu[nmove][6] = '\0';
   113				nmove++;
   114			}
   115	
   116			// quit:master終了(全workerにもquitが送られている)
   117			if ( ! strncmp( strline, "quit", 4 ) ) {
   118				break;
   119			}
   120	
   121			// 思考命令("7776FU"または"move")のとき("move 7776FU"は除く)
   122			if ( is_move( strline ) ||
   123				( ! strncmp( strline, "move", 4 ) && ( strline[4] != ' ' ) ) ) {
   124	
   125				// 初期化
   126				for ( i = 0; i < commSize; i++ ) {
   127					strcpy( strbuff[i], "" );
   128					strcpy( strinfo[i], "" );
   129					strcpy( strstat[i], "" );
   130					strcpy( strmove[i], "" );
   131					score[i] = 0;
   132				}
   133	
   134				// 全workerからの受信が終わるまで続ける
   135				numdone = 0;
   136				while ( numdone < commSize - 1 ) {
   137					// workerから受信(引数にMPI_ANY_SOURCE使用)
   138					MPI_Recv( recv, COMMSIZ, MPI_CHAR, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, &status );
   139					i = status.MPI_SOURCE;       // 送信元プロセス番号
   140	
   141					// 文字列連結(info/stat行は複数回に分けて送られてくることがある)
   142					strcat( strbuff[i], recv );
   143	
   144					// recvが改行を含むとき
   145					if ( strchr( recv, '\n' ) ) {
   146						// log
   147						fprintf( fp, recvfmt, nmove, i, strbuff[i] ); fflush( fp );
   148	
   149						// strbuff[i]が"info"で始まる("info tt"を除く)
   150						if ( ! strncmp( strbuff[i], "info", 4 )
   151						    && strncmp( strbuff[i], "info tt", 7 ) ) {
   152							// strinfo[i] : info行
   153							strcpy( strinfo[i], strbuff[i] );
   154						}
   155						// strbuff[i]が"stat"で始まる
   156						else if ( ! strncmp( strbuff[i], "stat", 4 ) ) {
   157							// strstat[i] : stat行
   158							strcpy( strstat[i], strbuff[i] );
   159						}
   160	
   161						// 初期化
   162						strcpy( strbuff[i], "" );
   163					}
   164	
   165					// 評価値(定跡"info 7776FU(80%)"は除く)
   166					if ( strncmp( strinfo[i], "info ", 5 ) ) {
   167						// strinfo[i]の"info"の次の数字 * 100
   168						score[i] = (int)( atof( strinfo[i] + 4 ) * 100 );
   169					}
   170	
   171					// recvが"move"で始まる
   172					if ( ! strncmp( recv, "move", 4 ) ) {
   173						strcpy( strmove[i], recv + 4 );  // ="7776FU\n"
   174					}
   175					// recvが"resign"で始まる
   176					else if ( ! strncmp( recv, "resign", 6 ) ) {
   177						strcpy( strmove[i], recv );      // ="resign\n"
   178					}
   179					// recvが"info tt"始まる
   180					else if ( ! strncmp( recv, "info tt", 7 ) ) {
   181						numdone++;
   182					}
   183				}
   184	
   185				// score[i]の最良(i=ibest)を探す(楽観的合議)、ibest=0のときは投了
   186				ibest = 0;
   187				sbest = ( nmove % 2 == 1 ) ? -100000 : +100000;
   188				for ( i = 1; i < commSize; i++ ) {
   189					if ( ! strncmp( strmove[i], "resign", 6 ) ) {
   190						// 投了は除く
   191					}
   192					else if ( ! strncmp( strinfo[i], "info ", 5 ) ) {
   193						// 定跡があればそれを採用する
   194						ibest = i;
   195						break;
   196					}
   197					else if ( ( ( nmove % 2 == 1 ) && ( score[i] > sbest ) )
   198					       || ( ( nmove % 2 == 0 ) && ( score[i] < sbest ) ) ) {
   199						ibest = i;
   200						sbest = score[i];
   201					}
   202				}
   203	
   204				if ( ibest == 0 ) {
   205					// 投了のときは標準出力に"resign"を出力する
   206					printf( "%s\n", "resign" ); fflush( stdout );
   207				}
   208				else {
   209					// 標準出力に最良workerの探索結果を出力する
   210					printf( "%s", strinfo[ibest] );
   211					printf( "%s", strstat[ibest] );
   212					printf( "move%s", strmove[ibest] );
   213					fflush( stdout );
   214	
   215					// 棋譜追加
   216					strncpy( strkifu[nmove], strmove[ibest], 6 );
   217					strkifu[nmove][6] = '\0';
   218	/*
   219					// 全workerの局面を本譜(最良手)で更新する
   220					for ( i = 1; i < commSize; i++ ) {
   221						// 1手前に戻す
   222						sprintf( send, "read . nil %d\n", nmove);
   223						MPI_Send( send, COMMSIZ, MPI_CHAR, i, 0, MPI_COMM_WORLD );
   224						fprintf( fp, sendfmt, nmove, i, send ); fflush( fp );
   225						// 局面を本譜で更新する
   226						sprintf( send, "move %s", strmove[ibest] );
   227						MPI_Send( send, COMMSIZ, MPI_CHAR, i, 0, MPI_COMM_WORLD );
   228						fprintf( fp, sendfmt, nmove, i, send ); fflush( fp );
   229					}
   230	*/
   231					// 全workerに最新局面を送信する
   232					for ( i = 1; i < commSize; i++ ) {
   233						strcpy( send, "new\n" );
   234						MPI_Send( send, COMMSIZ, MPI_CHAR, i, 0, MPI_COMM_WORLD );
   235						fprintf( fp, sendfmt, nmove, i, send ); fflush( fp );
   236						for ( n = 1; n <= nmove; n++ ) {
   237							sprintf( send, "move %s\n", strkifu[n] );
   238							MPI_Send( send, COMMSIZ, MPI_CHAR, i, 0, MPI_COMM_WORLD );
   239							fprintf( fp, sendfmt, nmove, i, send ); fflush( fp );
   240						}
   241					}
   242	
   243					// 手数++
   244					nmove++;
   245				}
   246			}
   247		}
   248	
   249		// logファイルを閉じる
   250		fclose( fp );
   251	
   252		// free
   253		for ( i = 0; i < commSize; i++ ) {
   254			free( strbuff[i] );
   255			free( strinfo[i] );
   256			free( strstat[i] );
   257			free( strmove[i] );
   258		}
   259		free( strbuff );
   260		free( strinfo );
   261		free( strstat );
   262		free( strmove );
   263		free( score );
   264	#endif
   265	}
   266	
   267	// CSA形式指し手(7776FU)であるか
   268	static int is_move( const char *str )
   269	{
   270		return ( strlen( str ) > 5 )
   271			&& isdigit( (int)str[0] ) && isdigit( (int)str[1] )
   272			&& isdigit( (int)str[2] ) && isdigit( (int)str[3] )
   273			&& isupper( (int)str[4] ) && isupper( (int)str[5] );
   274	}

局面の更新について
上のプログラムでは、231-241行のように毎回全workerに初手からの棋譜を送信しています。 このために通信遅延が最大0.01秒程度発生することに注意して下さい。
これを219-229行でコメントアウトしたように"read ."文を送信すると通信量は減らせますが、 一つのノードで複数のプロセスを起動するときはファイルgame.csaへの読み書きが競合し不具合が発生します。
USIプロトコルのように棋譜を一行で送ることができればこの問題は回避できます。

合議について
本ページにおけるMPIプログラムはSPMD(Single Program Multiple Data)ですので、 全プロセスが同じプログラムを実行します。 全workerがまったく同じプログラムを実行すると合議の意味がありませんので、 合議を行うためには、個々のworkerを異なるノードで実行し送受信仕様を変えずに探索部を変えるか、 上の87-92行でコメントアウトしたように、 各workerの思考条件を変えると1ノードで擬似的に合議を行うことができます。

4. コンパイル・リンク

コンパイル・リンクの方法は以下の通りです。
VC++では以下のコマンドを実行します。

$ nmake clean
$ nmake v0  (バージョン0プログラムbonanza_mpi_v0.exe作成)
$ nmake v1  (バージョン1プログラムbonanza_mpi_v1.exe作成)

(6) Makefile
FLAG = /DNDEBUG /DMINIMUM /DTLP /DHAVE_SSE2 /DINANIWA_SHIFT /DCSASHOGI /DNO_LOGGING /DDFPN /DDFPN_CLIENT

OBJS = data.obj main.obj io.obj proce.obj ini.obj utility.obj attack.obj\
       gencap.obj gennocap.obj gendrop.obj genevasn.obj mate3.obj genchk.obj\
       movgenex.obj makemove.obj unmake.obj time.obj csa.obj valid.obj\
       next.obj search.obj searchr.obj book.obj iterate.obj quiesrch.obj\
       swap.obj evaluate.obj root.obj hash.obj mate1ply.obj bitop.obj\
       rand.obj learn1.obj learn2.obj evaldiff.obj problem.obj ponder.obj\
       thread.obj sckt.obj debug.obj phash.obj dfpn.obj dfpnhash.obj

CC      = cl
CFLAGS  = $(FLAG) /Ox /nologo /W4 /wd4996
LDFLAGS = /Ox /nologo
LIBS    = ws2_32.lib msmpi.lib
MPIOPT  = /DMPI

OBJ_v0 = master_v0.obj
OBJ_v1 = master_v1.obj
PROGRAM_v0 = bonanza_mpi_v0.exe
PROGRAM_v1 = bonanza_mpi_v1.exe

v0:
	nmake /nologo $(PROGRAM_v0)
v1:
	nmake /nologo $(PROGRAM_v1)

$(PROGRAM_v0): $(OBJS) $(OBJ_v0)
	@echo "Loading $(PROGRAM_v0) ..."
	@$(CC) $(LDFLAGS) /Fe$(PROGRAM_v0) $(OBJS) $(OBJ_v0) $(LIBS)
$(PROGRAM_v1): $(OBJS) $(OBJ_v1)
	@echo "Loading $(PROGRAM_v1) ..."
	@$(CC) $(LDFLAGS) /Fe$(PROGRAM_v1) $(OBJS) $(OBJ_v1) $(LIBS)

main.obj:
	$(CC) /c $(CFLAGS) $(MPIOPT) $?
io.obj:
	$(CC) /c $(CFLAGS) $(MPIOPT) $?
master_v0.obj:
	$(CC) /c $(CFLAGS) $(MPIOPT) $?
master_v1.obj:
	$(CC) /c $(CFLAGS) $(MPIOPT) $?

.c.obj :
	$(CC) /c $(CFLAGS) $*.c

clean:
	del *.obj *.exe

5. 実行方法

5.1 コマンドラインでの実行方法

コマンドプロンプトを起動し、実行プログラムのあるフォルダに移動した後、 以下のコマンドを実行します。

バージョン0:
$ mpiexec -n 2 bonanza_mpi_v0.exe
このとき1masterと1workerが起動されます。

バージョン1:
$ mpiexec -n 3 bonanza_mpi_v1.exe
数字は3以上任意の数(=N)を入力して下さい。(意味はありませんがN=2でも構いません)
このとき1masterと(N-1)workerが起動されます。

以上の後、標準入力からコマンドを実行して下さい。 コマンドが多いときは予めコマンドをテキストファイルで作成し、 リダイレクションで実行して下さい。
例えば下記のようなテキストファイル"input.txt"を作成して実行すると下記のような結果が得られます。

input.txt
new
book off
ponder off
limit time 0 1
move
move
quit

$ mpiexec -n 2 bonanza_mpi_v0.exe < input.txt
Bonanza 6.0
info+1.83 +7776FU -3334FU +5968OU -2288UM +7988GI -5142OU
info-0.09 +7776FU -5142OU +5968OU -7162GI +3948GI -8384FU
info-0.07 +5756FU -3334FU +3948GI -8384FU +4857GI -8485FU
info+0.12 +5756FU -8384FU +7776FU -3334FU +3948GI -2288UM +7988GI -5142OU
info-0.74 +5756FU -8384FU +3948GI -3334FU +4857GI
info-0.60 +6978KI -3334FU +7776FU -4344FU +3948GI -3132GI +5969OU -3243GI
info+0.01 +7776FU -8384FU +6978KI -8485FU +3948GI -8586FU +8786FU -8286HI +5969OU -8676HI
info+1.32 +7776FU -3334FU +2726FU -8384FU +2625FU -8485FU +2524FU -2324FU +2824HI -4132KI
info+1.40 +7776FU -8384FU +6978KI -8485FU +2726FU -8586FU +8786FU -8286HI +2625FU -8676HI +8877KA
info+1.77 +7776FU -8384FU +7968GI -6364FU +3948GI -7162GI +5756FU -5142OU
info+1.08 +7776FU -8384FU +2726FU -3334FU +2625FU -8485FU +2524FU -2324FU +2824HI -4132KI +6978KI -8586FU +8786FU -8286HI +2434HI -2288UM +7988GI -8676HI
statsatu=6 cpu=100 nps=426.84
move7776FU
info tt 000:01
info+0.00 -3334FU +5968OU -5142OU +3948GI -7162GI
info+2.05 -3334FU +5968OU -5142OU +3948GI -2288UM +7988GI -7162GI
info+1.99 -8384FU +2726FU -8485FU +8877KA -3334FU +7988GI
info+1.07 -5142OU +7968GI -3334FU +6877GI -7162GI +3948GI
info+2.65 -5142OU +2726FU -3334FU +2625FU -7162GI +2524FU -2288UM +7988GI
info+0.03 -8384FU +7968GI -8485FU +6877GI -3334FU +6978KI -5142OU
info+1.52 -8384FU +7968GI -3334FU +6877GI -5142OU +2726FU -7162GI +2625FU
info+1.40 -8384FU +2726FU -8485FU +2625FU -8586FU +8786FU -8286HI +6978KI -8676HI +8877KA
info+1.11 -8384FU +7968GI -7162GI +6978KI -6364FU +6877GI -6263GI +5756FU -3334FU +3948GI
info+1.08 -8384FU +7978GI -7162GI +2726FU -3334FU +7877GI -5142OU +2625FU
info+1.58 -8384FU +7978GI -3334FU +7877GI -7162GI +3948GI -3142GI +2726FU -5354FU +5756FU -4132KI +2625FU
info+1.58 -3334FU [-0!]
statsatu=10 cpu=100 nps=430.67
move3334FU
info tt 000:01

$ mpiexec -n 3 bonanza_mpi_v1.exe < input.txt
Bonanza 6.0
info+1.08 +7776FU -8384FU +2726FU -3334FU +2625FU -8485FU +2524FU -2324FU +2824HI -4132KI +6978KI -8586FU +8786FU -8286HI +2434HI -2288UM +7988GI -8676HI
statsatu=5 cpu=100 nps=413.76
move7776FU
info+1.55 -3334FU [-0!]
statsatu=11 cpu=100 nps=380.21
move3334FU

BonanzaのコマンドについてはBonanza付属のreadme.txtを参考にして下さい。
mpiexecの詳しい使い方についてはコマンド"mpiexec"または"mpiexec -help2"または"mpiexec -help3"を参考にして下さい。
以上の方法は主にデバック時に使用します。

5.2 プチ将棋での実行方法

実際に将棋の対局を行うにはGUIが必要になります。 上で述べたようにmpiexecは引数が必須ですが、 引数に対応したBonanza用GUIに プチ将棋 があります。
図1はプチ将棋の[ツール]→[エンジン管理]メニューでMPI版Bonanzaを登録する方法です。
[エンジン名]には適当な名前を、 [実行プログラム]にはGUIが直接起動するランチャー"mpiexec.exe"を指定して下さい。 [プロトコル]は"Bonanza"を選択して下さい。


図1 プチ将棋でのエンジン登録

図2はプチ将棋で対局を開始するときの設定です。 [引数]に5.1節のコマンドの引数、すなわちmpiexec以降を入力して下さい。
[予測読み]は無効ですのでOFFにして下さい。 またバージョン1では[持ち時間]は無効です。 それ以外の設定は有効であり、各workerに送られ探索条件に反映されます。


図2 プチ将棋での対局開始

5.3 複数台で実行する方法

分散メモリー環境(クラスタ環境)
MPIプログラムは複数台の環境で動かすことが普通です。 これを分散メモリー環境(または、クラスタ環境)と呼びます。
MPIでは計算を実行するコンピューターの単位を"ノード"と呼びます。 ノードはネットワークに接続されており、1個以上のCPUとメモリーを持ち、 各CPUは複数のコアを持っています。
GUIを動かすノード(ユーザーが操作するノード)を特にルートノードと呼びます。 そのネットワーク名は"localhost"となります。
これに複数台のノードが接続されており、そのホスト名(=ノードの名前)を順にPC1, PC2,...とします。
なお、ホスト名はWindowsでは[ネットワークコンピュータ]に表示される名前です。 またはそれぞれのPCでコマンド"hostname"を実行するとホスト名が表示されます。

ファイルのコピー
MPIプログラムはすべてのノードの同じ絶対パスに同じ実行プログラムがあることを基本とします。
作業ノード(通常はルートノード)で作成した実行プログラム (bonanza_mpi_v0.exeまたはbonanza_mpi_v1.exe) をすべてのノードの同じ絶対パスにコピーして下さい。
さらに、msmpi.dll と smpd.exe も同じパスにコピーして下さい。
また、Bonanzaの実行に必要なファイル(book.bin, fv.bin)も同じパスにコピーして下さい。

引数の指定方法
引数の例は以下のようになります。
-hosts 2 localhost 3 PC1 2 bonanza_mpi_v1.exe   (ルートノードで3プロセス(1master+2worker)、ノードPC1で2プロセス(2worker)を起動)
-hosts 3 localhost 3 PC1 2 PC2 2 bonanza_mpi_v1.exe   (ルートノードで3プロセス(1master+2worker)、ノードPC1で2プロセス(2worker)、ノードPC2で2プロセス(2worker)を起動)

ノード数が多くて引数が長くなるときは引数を以下のようにすることができます。
-configfile mpi.cfg
ここでmpi.cfgは任意の名前であり、以下のように引数を(必要なら\で区切って)記述したテキストファイルです。

mpi.cfg
-hosts 3 \
localhost 3 \
PC1 2 \
PC2 2 \
bonanza_mpi_v1.exe

ノード、プロセス、スレッドの関係は、
ノード >= プロセス >= スレッド
です。一つのノードは一つ以上のプロセスを実行することができます。 一つのプロセスは一つ以上のスレッドを実行することができます。 逆に、複数のノードにまたがったプロセス、複数のプロセスにまたがったスレッドは実行できません。

MPIデーモンの起動
複数台でMPIプログラムを実行するには、全ノードでコマンドプロンプトを起動し、 以下のコマンドを実行して下さい。
$ smpd -p 8677
このときファイアウォールが警告を出したら許可して下さい。 ここで8677はMPIが使用するポート番号(固定)です。 ウィンドウには何も表示されないのが正常な状態です。
プログラム実行時に多数のメッセージ(文字化けも含む)が表示されたときは、 通信に何らかのエラー発生したことを表しますので、 全ノードでCtrl-Cを入力してsmpdを終了し、 ルートノードのタスクマネージャーのmasterプログラム(使用メモリーが小さいもの) を終了して下さい。そのとき全workerも終了されます。 workerが終了されないときはタスクマネージャーで終了して下さい。 (MPIプログラムは障害発生時は回復不可能です)
なお、1台で使用するときはsmpdを陽に起動する必要はありませんが、 これは内部でsmpdが自動的に起動されるためです。

プログラムの実行
以上の準備の後、プチ将棋で対局を開始することができます。 連続対局も可能です。 また外部の対局サーバーとのネットワーク対局も可能です。

トップページへ