From 1a558567a363f7a923d5d0362eade9fc24856a99 Mon Sep 17 00:00:00 2001 From: Jean-loup Gailly Date: Thu, 1 Apr 2010 21:59:13 +0200 Subject: [PATCH] Distributed engine: exchange played_games between master and slaves to fix test for early exit and display correct games/s. --- distributed/distributed.c | 86 ++++++++++++++++++++++++----------------------- uct/internal.h | 2 ++ uct/uct.c | 23 ++++++++----- 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/distributed/distributed.c b/distributed/distributed.c index 02afb21..00c748f 100644 --- a/distributed/distributed.c +++ b/distributed/distributed.c @@ -486,13 +486,13 @@ distributed_notify(struct engine *e, struct board *b, int id, char *cmd, char *a return P_OK; } -/* genmoves returns a line "=id total_playouts threads keep_looking[ reserved]" +/* genmoves returns a line "=id played_own total_playouts threads keep_looking[ reserved]" * then a list of lines "coord playouts value". * Return the move with most playouts, and additional stats. * Keep this code in sync with uct_getstats(). * slave_lock is held on entry and on return. */ static coord_t -select_best_move(struct board *b, struct move_stats *stats, +select_best_move(struct board *b, struct move_stats *stats, int *played, int *total_playouts, int *total_threads, bool *keep_looking) { assert(reply_count > 0); @@ -502,14 +502,16 @@ select_best_move(struct board *b, struct move_stats *stats, coord_t best_move = pass; int best_playouts = -1; + *played = 0; *total_playouts = 0; *total_threads = 0; int keep = 0; for (int reply = 0; reply < reply_count; reply++) { char *r = gtp_replies[reply]; - int id, p, t, k; - if (sscanf(r, "=%d %d %d %d", &id, &p, &t, &k) != 4) continue; + int id, o, p, t, k; + if (sscanf(r, "=%d %d %d %d %d", &id, &o, &p, &t, &k) != 5) continue; + *played += o; *total_playouts += p; *total_threads += t; keep += k; @@ -533,18 +535,33 @@ select_best_move(struct board *b, struct move_stats *stats, return best_move; } -/* Write in s the stats from all slaves above min_playouts, except for pass and resign. - * s must have room up to end and upon return ends with an empty line. +/* Set the args for the genmoves command. If stats is not null, + * append the stats from all slaves above min_playouts, except + * for pass and resign. args must have CMDS_SIZE bytes and + * upon return ends with an empty line. + * Keep this code in sync with uct_genmoves(). * slave_lock is held on entry and on return. */ static void -write_stats(struct board *b, struct move_stats *stats, char *s, char *end, int min_playouts) +genmoves_args(char *args, struct board *b, enum stone color, int played, + struct time_info *ti, struct move_stats *stats, int min_playouts) { - foreach_point(b) { - if (stats[c].playouts <= min_playouts) continue; - s += snprintf(s, end - s, "%s %d %.7f\n", - coord2sstr(c, b), - stats[c].playouts, stats[c].value); - } foreach_point_end; + char *end = args + CMDS_SIZE; + char *s = args + snprintf(args, CMDS_SIZE, "%s %d", stone2str(color), played); + + if (ti->dim == TD_WALLTIME) { + s += snprintf(s, end - s, " %.3f %.3f %d %d", + ti->len.t.main_time, ti->len.t.byoyomi_time, + ti->len.t.byoyomi_periods, ti->len.t.byoyomi_stones); + } + s += snprintf(s, end - s, "\n"); + if (stats) { + foreach_point(b) { + if (stats[c].playouts <= min_playouts) continue; + s += snprintf(s, end - s, "%s %d %.7f\n", + coord2sstr(c, b), + stats[c].playouts, stats[c].value); + } foreach_point_end; + } s += snprintf(s, end - s, "\n"); } @@ -553,7 +570,8 @@ write_stats(struct board *b, struct move_stats *stats, char *s, char *end, int m #define YOSE_START 40 static coord_t * -distributed_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive) +distributed_genmove(struct engine *e, struct board *b, struct time_info *ti, + enum stone color, bool pass_all_alive) { struct distributed *dist = e->data; double now = time_now(); @@ -561,27 +579,17 @@ distributed_genmove(struct engine *e, struct board *b, struct time_info *ti, enu char *cmd = pass_all_alive ? "pachi-genmoves_cleanup" : "pachi-genmoves"; char args[CMDS_SIZE]; - char *end = args + sizeof(args); coord_t best; - int playouts, threads; + int played, playouts, threads; if (ti->period == TT_NULL) *ti = default_ti; struct time_stop stop; time_stop_conditions(ti, b, FUSEKI_END, YOSE_START, &stop); struct time_info saved_ti = *ti; - /* Send the first genmoves without stats. This is - * a multi-line command ending with \n\n. - * Keep this code in sync with uct_genmoves(). */ - char *col = args + snprintf(args, sizeof(args), "%s", stone2str(color)); - char *s = col; - if (ti->dim == TD_WALLTIME) { - s += snprintf(s, end - s, " %.3f %.3f %d %d", - ti->len.t.main_time, ti->len.t.byoyomi_time, - ti->len.t.byoyomi_periods, ti->len.t.byoyomi_stones); - } - s += snprintf(s, end - s, "\n\n"); + /* Send the first genmoves without stats. */ + genmoves_args(args, b, color, 0, ti, NULL, 0); /* Combined move stats from all slaves, only for children * of the root node, plus 2 for pass and resign. */ @@ -596,24 +604,17 @@ distributed_genmove(struct engine *e, struct board *b, struct time_info *ti, enu double start = now; get_replies(now + STATS_UPDATE_INTERVAL); now = time_now(); - s = col; - if (ti->dim == TD_WALLTIME) { + if (ti->dim == TD_WALLTIME) time_sub(ti, now - start); - s += snprintf(s, end - s, " %.3f %.3f %d %d", - ti->len.t.main_time, ti->len.t.byoyomi_time, - ti->len.t.byoyomi_periods, ti->len.t.byoyomi_stones); - } - s += snprintf(s, end - s, "\n"); - bool keep_looking; - best = select_best_move(b, stats, &playouts, &threads, &keep_looking); - write_stats(b, stats, s, end, stats[best].playouts / 100); + bool keep_looking; + best = select_best_move(b, stats, &played, &playouts, &threads, &keep_looking); if (!keep_looking) break; if (ti->dim == TD_WALLTIME) { if (now - ti->len.t.timer_start >= stop.worst.time) break; } else { - if (playouts >= stop.worst.playouts) break; + if (played >= stop.worst.playouts) break; } if (DEBUGL(2)) { char buf[BSIZE]; @@ -627,6 +628,7 @@ distributed_genmove(struct engine *e, struct board *b, struct time_info *ti, enu } /* Send the command with the same gtp id, to avoid discarding * a reply to a previous genmoves at the same move. */ + genmoves_args(args, b, color, played, ti, stats, stats[best].playouts / 100); update_cmd(b, cmd, args, false); } int replies = reply_count; @@ -650,12 +652,12 @@ distributed_genmove(struct engine *e, struct board *b, struct time_info *ti, enu double time = now - first + 0.000001; /* avoid divide by zero */ snprintf(buf, sizeof(buf), "GLOBAL WINNER is %s %s with score %1.4f (%d/%d games)\n" - "genmove in %0.2fs %d slaves %d threads (%d games/s," + "genmove %d games in %0.2fs %d slaves %d threads (%d games/s," " %d games/s/slave, %d games/s/thread)\n", stone2str(color), coord, get_value(stats[best].value, color), - stats[best].playouts, playouts, time, replies, threads, - (int)(playouts/time), (int)(playouts/time/replies), - (int)(playouts/time/threads)); + stats[best].playouts, playouts, played, time, replies, threads, + (int)(played/time), (int)(played/time/replies), + (int)(played/time/threads)); logline(NULL, "* ", buf); } free(coord); diff --git a/uct/internal.h b/uct/internal.h index 146725c..1b87772 100644 --- a/uct/internal.h +++ b/uct/internal.h @@ -88,6 +88,8 @@ struct uct { /* Used within frame of single genmove. */ struct board_ownermap ownermap; struct node_stats *stats; + int played_own; + int played_all; /* games played by all slaves */ /* Game state - maintained by setup_state(), reset_state(). */ struct tree *t; diff --git a/uct/uct.c b/uct/uct.c index 3b1013e..3e0f996 100644 --- a/uct/uct.c +++ b/uct/uct.c @@ -119,6 +119,7 @@ prepare_move(struct engine *e, struct board *b, enum stone color) u->ownermap.playouts = 0; memset(u->ownermap.map, 0, board_size2(b) * sizeof(u->ownermap.map[0])); memset(u->stats, 0, board_size2(b) * sizeof(u->stats[0])); + u->played_own = u->played_all = 0; } static void @@ -488,7 +489,7 @@ static bool uct_search_stop_early(struct uct *u, struct tree *t, struct board *b, struct time_info *ti, struct time_stop *stop, struct tree_node *best, struct tree_node *best2, - int base_playouts, int i) + int played) { /* Always use at least half the desired time. It is silly * to lose a won game because we played a bad move in 0.1s. */ @@ -512,7 +513,7 @@ uct_search_stop_early(struct uct *u, struct tree *t, struct board *b, bool time_indulgent = (!ti->len.t.main_time && ti->len.t.byoyomi_stones == 1); if (best2 && ti->dim == TD_WALLTIME && !time_indulgent) { double remaining = stop->worst.time - elapsed; - double pps = ((double)i - base_playouts) / elapsed; + double pps = ((double)played) / elapsed; double estplayouts = remaining * pps + PLAYOUT_DELTA_SAFEMARGIN; if (best->u.playouts > best2->u.playouts + estplayouts) { if (UDEBUGL(2)) @@ -685,7 +686,8 @@ uct_search(struct uct *u, struct board *b, struct time_info *ti, enum stone colo if (best) best2 = u->policy->choose(u->policy, ctx->t->root, b, color, best->coord); /* Possibly stop search early if it's no use to try on. */ - if (best && uct_search_stop_early(u, ctx->t, b, ti, &stop, best, best2, base_playouts, i)) + int played = u->played_all + i - base_playouts; + if (best && uct_search_stop_early(u, ctx->t, b, ti, &stop, best, best2, played)) break; /* Check against time settings. */ @@ -830,6 +832,7 @@ uct_bestmove(struct engine *e, struct board *b, struct time_info *ti, enum stone int base_playouts = u->t->root->u.playouts; /* Start or continue the Monte Carlo Tree Search! */ int played_games = uct_search(u, b, ti, color, u->t, keep_looking); + u->played_own += played_games; /* Choose the best move from the tree. */ struct tree_node *best = u->policy->choose(u->policy, u->t->root, b, color, resign); @@ -920,7 +923,7 @@ uct_getstats(struct uct *u, struct board *b, coord_t c, bool keep_looking) char *r = reply; char *end = reply + sizeof(reply); struct tree_node *root = u->t->root; - r += snprintf(r, end - r, "%d %d %d", root->u.playouts, u->threads, keep_looking); + r += snprintf(r, end - r, "%d %d %d %d", u->played_own, root->u.playouts, u->threads, keep_looking); int min_playouts = root->u.playouts / 100; // Give a large weight to pass or resign, but still allow other moves. @@ -978,12 +981,14 @@ uct_genmoves(struct engine *e, struct board *b, struct time_info *ti, enum stone struct uct *u = e->data; assert(u->slave); - /* Get correct time from master. + /* Get playouts and time information from master. * Keep this code in sync with distributed_genmove(). */ - if (ti->dim == TD_WALLTIME - && sscanf(args, "%lf %lf %d %d", &ti->len.t.main_time, - &ti->len.t.byoyomi_time, &ti->len.t.byoyomi_periods, - &ti->len.t.byoyomi_stones) != 4) { + if ((ti->dim == TD_WALLTIME + && sscanf(args, "%d %lf %lf %d %d", &u->played_all, &ti->len.t.main_time, + &ti->len.t.byoyomi_time, &ti->len.t.byoyomi_periods, + &ti->len.t.byoyomi_stones) != 5) + + || (ti->dim == TD_GAMES && sscanf(args, "%d", &u->played_all) != 1)) { return NULL; } -- 2.11.4.GIT