6 /* Move statistics; we track how good value each move has. */
7 /* These operations are supposed to be atomic - reasonably
8 * safe to perform by multiple threads at once on the same stats.
9 * What this means in practice is that perhaps the value will get
10 * slightly wrong, but not drastically corrupted. */
13 int playouts
; // # of playouts
14 float value
; // BLACK wins/playouts
17 /* Add a result to the stats. */
18 static void stats_add_result(struct move_stats
*s
, float result
, int playouts
);
20 /* Remove a result from the stats. */
21 static void stats_rm_result(struct move_stats
*s
, float result
, int playouts
);
23 /* Merge two stats together. THIS IS NOT ATOMIC! */
24 static void stats_merge(struct move_stats
*dest
, struct move_stats
*src
);
26 /* Reverse stats parity. */
27 static void stats_reverse_parity(struct move_stats
*s
);
29 /* Temper value based on parent value in specified way - the value should be
30 * usable standalone then, representing an improvement against parent value. */
31 static float stats_temper_value(float val
, float pval
, int mode
);
34 /* We actually do the atomicity in a pretty hackish way - we simply
35 * rely on the fact that int,float operations should be atomic with
36 * reasonable compilers (gcc) on reasonable architectures (i386,
38 /* There is a write order dependency - when we bump the playouts,
39 * our value must be already correct, otherwise the node will receive
40 * invalid evaluation if that's made in parallel, esp. when
41 * current s->playouts is zero. */
44 stats_add_result(struct move_stats
*s
, float result
, int playouts
)
46 int s_playouts
= s
->playouts
;
47 float s_value
= s
->value
;
48 /* Force the load, another thread can work on the
49 * values in parallel. */
50 __sync_synchronize(); /* full memory barrier */
52 s_playouts
+= playouts
;
53 s_value
+= (result
- s_value
) * playouts
/ s_playouts
;
55 /* We rely on the fact that these two assignments are atomic. */
57 __sync_synchronize(); /* full memory barrier */
58 s
->playouts
= s_playouts
;
62 stats_rm_result(struct move_stats
*s
, float result
, int playouts
)
64 if (s
->playouts
> playouts
) {
65 int s_playouts
= s
->playouts
;
66 float s_value
= s
->value
;
67 /* Force the load, another thread can work on the
68 * values in parallel. */
69 __sync_synchronize(); /* full memory barrier */
71 s_playouts
-= playouts
;
72 s_value
+= (s_value
- result
) * playouts
/ s_playouts
;
74 /* We rely on the fact that these two assignments are atomic. */
76 __sync_synchronize(); /* full memory barrier */
77 s
->playouts
= s_playouts
;
80 /* We don't touch the value, since in parallel, another
81 * thread can be adding a result, thus raising the
82 * playouts count after we zero the value. Instead,
83 * leaving the value as is with zero playouts should
84 * not break anything. */
90 stats_merge(struct move_stats
*dest
, struct move_stats
*src
)
92 /* In a sense, this is non-atomic version of stats_add_result(). */
94 dest
->playouts
+= src
->playouts
;
95 dest
->value
+= (src
->value
- dest
->value
) * src
->playouts
/ dest
->playouts
;
100 stats_reverse_parity(struct move_stats
*s
)
102 s
->value
= 1 - s
->value
;
106 stats_temper_value(float val
, float pval
, int mode
)
109 float expd
= val
- pval
;
111 case 1: /* no tempering */
114 case 2: /* 0.5+(result-expected)/2 */
115 tval
= 0.5 + expd
/ 2;
117 case 3: { /* 0.5+bzz((result-expected)^2) */
118 float ntval
= expd
* expd
;
119 /* val = 1 pval = 0.8 : ntval = 0.04 tval = 0.54
120 * val = 1 pval = 0.6 : ntval = 0.16 tval = 0.66
121 * val = 1 pval = 0.3 : ntval = 0.49 tval = 0.99
122 * val = 1 pval = 0.1 : ntval = 0.81 tval = 1.31 */
123 tval
= 0.5 + (val
> 0.5 ? 1 : -1) * ntval
;
125 case 4: /* 0.5+sqrt(result-expected)/2 */
126 tval
= 0.5 + copysignf(sqrt(fabs(expd
)), expd
) / 2;
128 default: assert(0); break;