我正在为我要参加的大学的锦标赛编写Kalah AI。 在开发过程中,我偶然发现了一些不确定的行为,这些行为取决于转置表的大小,这取决于我无法向自己解释的计算时间的变化。本质上,问题在于:增加转置表的大小会减少搜索到的位置数量,但不会减少搜索这些位置所花费的时间。
转位表本质上是一个哈希表,已搜索的位置及其搜索结果存储在该哈希表中。这类似于动态编程,在此过程中您要用计算时间来交换内存。 因此,直观地讲,增加可用内存将加快计算速度。在下面,您可以查看控制换位表行为的代码。 (是的,程序是用Java编写的。我知道在C ++中这样会更容易,但是要参与Java程序是必须的)
初始化,在搜索之前调用
public TranspositionTable(int power) {
entries = (int) Math.pow(2, power);
table = new Entry[entries];
hashmask = entries - 1;
for(int i = 0; i < table.length; i++) {
table[i] = new Entry();
table[i].depth = Byte.MIN_VALUE;
}
}
写访问权限:
public void add(NodeType type, short value, byte bestMove, byte depth, long hash) {
int idx = (int) (hash & hashmask);
Entry tte = table[idx];
synchronized (tte) {
if(!tte.used) {
tte.used = true;
curEntries++;
rewrites--;
}
//don't overwrite higher search depth entries with lower ones.
if(tte.depth <= depth){
rewrites++;
tte.hash = hash;
tte.type = type;
tte.value = value;
tte.bestMove = bestMove;
tte.depth = depth;
}
}
}
请注意,我目前仅使用一个线程进行搜索,因此同步语句目前是徒劳的。
读取权限:
public Entry get(long hash) {
int idx = (int) (hash & hashmask);
Entry e = table[idx];
synchronized (e) {
if(!e.used) {
misses++;
return null;
}
if(hash == e.hash) {
accesses++;
return e;
}
}
misses++;
return null;
}
我正在使用64位Jenkins哈希函数生成密钥。
调用转座表是在迭代加深的alpha-beta搜索中进行的,它使用吸引窗口,静默搜索和其他一些与此主题可能无关的优化方法。搜索过程如下所示:
public static int search(int alpha, int beta, int stackIdx, MutableState node, int depth, TranspositionTable tt) {
...
long hash = tt.getHash(node);
Entry e = tt.get(hash);
if (e != null) {// entry is valid?
if (e.depth >= depth) {
switch (e.type) {
case EXACT:
ss[stackIdx].pvIdx = e.bestMove;
return e.value;
case LOWERBOUND:
if (e.value > alpha)
alpha = e.value;
break;
case UPPERBOUND:
if (e.value < beta)
beta = e.value;
break;
}
if (alpha >= beta) {
ss[stackIdx].pvIdx = e.bestMove;
return alpha;
}
}
}
...
// store node in TT
NodeType type;
if (best <= alphaOriginal) {
type = NodeType.UPPERBOUND;
} else if (best >= beta) {
type = NodeType.LOWERBOUND;
} else {
type = NodeType.EXACT;
}
tt.add(type, (short) bestValue, (byte) bestMove, (byte) depth, hash);
return best;
}
下面列出了选定的搜索和转置表访问度量,显示了三个运行平均的经过时间(毫秒),搜索到的位置数量,转置表使用的条目,负载(使用的条目除以总条目),成功访问,重写(从Kalah(7,7)起始位置进行的13次半深度深度搜索中捕获的表大小不等的未命中的错误。
table size 2^20:
Passed: 5581 ms
nodesSearched 6_781_473
qsnodesSearched 10_355_860
TT: entries: 966_427
TT: load: 0.9216566
TT: accesses: 426_747
TT: rewrites: 1_479_636
TT: misses: 2_800_001
table size 2^21:
Passed: 5473 ms
nodesSearched 6_571_630
qsnodesSearched 10_156_993
TT: entries: 1_487_042
TT: load: 0.7090769
TT: accesses: 471_566
TT: rewrites: 1_174_011
TT: misses: 2_665_098
table size 2^22:
Passed: 5688 ms
nodesSearched 6_449_400
qsnodesSearched 10_019_367
TT: entries: 1_908_758
TT: load: 0.45508337
TT: accesses: 501_231
TT: rewrites: 863_635
TT: misses: 2_585_731
table size 2^23:
Passed: 5667 ms
nodesSearched 6_380_913
qsnodesSearched 9_945_442
TT: entries: 2_174_936
TT: load: 0.25927258
TT: accesses: 517_884
TT: rewrites: 649_352
TT: misses: 2_538_265
table size 2^24:
Passed: 6015 ms
nodesSearched 6_356_761
qsnodesSearched 9_920_876
TT: entries: 2_329_595
TT: load: 0.13885468
TT: accesses: 526_862
TT: rewrites: 525_318
TT: misses: 2_519_007
table size 2^25:
Passed: 5924 ms
nodesSearched 6_332_790
qsnodesSearched 9_895_421
TT: entries: 2_408_029
TT: load: 0.07176486
TT: accesses: 531_226
TT: rewrites: 457_211
TT: misses: 2_504_210
注意:
您可能已经观察到,随着表大小的增加,搜索到的节点数量会减少,但是经过的时间却没有。
我的第一个理论是,Java中的数组索引(计算特定数组项的内存地址)会随着数组大小的增加而花费更多的时间,通过使节点的搜索成本更高,从而使具有较小搜索树的速度无效。但我觉得情况并非如此,因为您希望搜索结果具有线性。
因此,我的问题是:是什么原因导致换位表尺寸增加时搜索花费更长的时间?
谢谢! :)