23

好的,所以我已经在我的国际象棋程序上工作了一段时间,我开始碰壁了。我已经完成了所有标准优化(negascout、迭代深化、杀手移动、历史启发式、静态搜索、棋子位置评估、一些搜索扩展),但我完全没有想法!

我希望尽快让它成为多线程的,这应该会给我的性能带来很大的提升,但除此之外,你们还遇到过其他漂亮的技巧吗?我曾考虑改用 MDF(f),但我听说这很麻烦,而且不值得。

我最感兴趣的是某种学习算法,但我不知道是否有人用国际象棋程序有效地做到了这一点。

另外,切换到位板会很重要吗?我目前正在使用 0x88。

4

12 回答 12

27

在我的国际象棋引擎 ( www.chessbin.com ) 开发的最后一年中,大部分时间都花在优化我的代码上,以便更好更快地进行移动搜索。在那段时间里,我学到了一些技巧,我想与你分享。

衡量绩效

本质上,您可以通过两种方式提高性能:

  • 更快地评估您的节点
  • 搜索更少的节点以得出相同的答案

您在代码优化中的第一个问题将是测量。你怎么知道你真的有所作为?为了帮助您解决这个问题,您需要确保可以在移动搜索过程中记录一些统计数据。我在国际象棋引擎中捕获的是:

  • 完成搜索所需的时间。
  • 搜索的节点数

这将允许您对更改进行基准测试和测试。进行测试的最佳方法是从开局、中局和终局创建多个保存游戏。记录黑白搜索的时间和节点数。在进行任何更改后,我通常会针对上述保存游戏进行测试,看看我是否在上述两个矩阵中做出了改进:搜索的节点数或速度。

更复杂的是,在进行代码更改后,您可能会运行引擎 3 次,每次都会得到 3 个不同的结果。假设您的国际象棋引擎在 9、10 和 11 秒内找到了最佳走法。这是大约 20% 的价差。那么你是把你的引擎提高了 10%-20% 还是只是改变了你电脑上的负载。你怎么知道的?为了解决这个问题,我添加了一些方法,可以让我的引擎与自己对战,它会为白色和黑色做出动作。通过这种方式,您不仅可以测试一个动作的时间变化,还可以测试游戏过程中多达 50 个动作的一系列动作。如果上次游戏需要 10 分钟,而现在需要 9 分钟,那么您的引擎可能提高了 10%。再次运行测试应该可以确认这一点。

寻找性能增益

既然我们知道如何衡量性能提升,让我们讨论如何识别潜在的性能提升。

如果您在 .NET 环境中,那么 .NET 分析器将是您的朋友。如果您有 Visual Studio for Developers 版本,它是免费内置的,但是您可以使用其他第三方工具。这个工具为我节省了几个小时的工作,因为它会告诉你你的引擎大部分时间都花在了哪里,并让你专注于你的问题点。如果您没有分析器工具,您可能必须以某种方式记录时间戳,因为您的引擎会经历不同的步骤。我不建议这样做。在这种情况下,一个好的分析器是物超所值的。Red Gate ANTS Profiler 很贵,但是我试过的最好的。如果你买不起,至少在他们的 14 天试用期使用它。

您的分析器会为您明确地识别事物,但是这里有一些我在使用 C# 时学到的小教训:

  • 将所有内容设为私有
  • 无论你不能将其设为私有,将其密封
  • 使尽可能多的方法成为静态的。
  • 不要让你的方法变得冗长,一个长的方法比 4 个小的方法好。
  • 存储为数组 [8][8] 的棋盘比 [64] 的数组慢
  • 尽可能将 int 替换为 byte。
  • 尽早从您的方法中返回。
  • 堆栈比列表好
  • 数组比堆栈和列表更好。
  • 如果您可以在填充列表之前定义列表的大小。
  • 铸造,拳击,拆箱是邪恶的。

进一步的性能提升:

我发现移动生成和排序非常重要。然而,这是我所看到的问题。如果您在排序和运行 Alpha Beta 之前评估每个动作的得分,您将能够优化您的动作顺序,以便您将获得极快的 Alpha Beta 截止。这是因为您将能够首先尝试最好的动作。但是,您花费在评估每个动作上的时间将被浪费掉。例如,您可能已经评估了 20 个动作的得分,对您的动作进行排序尝试前 2 个动作并收到第 2 个动作的截止值。理论上,您在其他 18 个动作上花费的时间被浪费了。

另一方面,如果您进行更轻松、更快速的评估,例如仅捕获,您的排序将不会那么好,您将不得不搜索更多节点(最多 60% 以上)。另一方面,您不会对每一个可能的举动都进行严格的评估。总的来说,这种方法通常更快

在拥有足够信息以进行良好排序和不对您不会使用的移动进行额外工作之间找到这种完美平衡,将使您在搜索算法中获得巨大收益。此外,如果您选择较差的排序方法,您将希望首先进行较浅的搜索,例如第 3 层,在进行更深入的搜索之前对您的移动进行排序(这通常称为迭代深化)。这将显着改善您的排序并允许您搜索更少的移动。

于 2009-07-13T19:10:28.597 回答
6

回答一个老问题。

假设您已经有一个工作转置表。

后期移动减少。这给了我的程序大约 100 个 elo 点,而且实现起来非常简单。

根据我的经验,除非您的实现效率非常低,否则实际的板表示(0x88、位板等)并不那么重要。

虽然你可以用糟糕的性能削弱你的国际象棋引擎,但闪电般的快速移动生成器本身并不能使程序变得好。

使用的搜索技巧和评估功能是决定整体实力的压倒性因素。

到目前为止,评估中最重要的部分是材料、通过棋子、国王安全和棋子结构。

搜索中最重要的部分是:Null Move Pruning、Check Extension 和 Late Move 减少。

仅凭这些简单的技术,您的程序就可以取得长足的进步!

于 2009-10-05T14:23:33.877 回答
3

好搬家下单!

一个老问题,但现在与 5 年前相同的技术适用。我们不是都在编写自己的国际象棋引擎吗,我有我自己的名为“Norwegian Gambit”的引擎,我希望它最终能与 CCRL 上的其他 Java 引擎竞争。我和其他许多人一样使用 Stockfish 来获取想法,因为它写得很好,而且很开放。他们的测试框架 Fishtest 和它的社区也提供了大量的好建议。将您的评估分数与 Stockfish 得到的结果进行比较是值得的,因为如何评估可能仍然是国际象棋编程中最大的未知数,而且 Stockfish 已经远离了许多已成为都市传说的传统评估(如双主教奖金)。然而,最大的不同是在我实施了与您提到的相同的技术之后,Negascout、TT、LMR、

搬家订购要领

容易忘记的一件事是良好的移动顺序。为了使 Alpha Beta 截止有效,必须首先获得最佳移动。另一方面,它也可能很耗时,因此必须仅在必要时进行。

  1. 换位表
  2. 按收益对促销和良好捕获进行排序
  3. 杀手锏
  4. 导致检查对手的动作
  5. 历史启发式
  6. 无声动作 - 按 PSQT 值排序

排序应该根据需要进行,通常对捕获进行排序就足够了,之后您可以仅在需要时运行更昂贵的检查排序和 PSQT。

关于 Java/C# 与 C/C++/Assembly

Java 的编程技术与使用 C# 的 Adam Berent 的出色回答中的相同。除了他的列表,我会提到避免使用对象数组,而是使用许多原语数组,但与他使用字节的建议相反,我发现使用 64 位 java 几乎没有可以使用 byte 和 int 而不是 64 位长来保存。我也走上了重写 C/C++/Assembly 的道路,但我没有任何性能提升。我使用了 LZCNT 和 POPCNT 等位扫描指令的汇编代码,但后来我发现 Java 8 也使用这些而不是 Long 对象上的方法。令我惊讶的是,Java 速度更快,Java 8 虚拟机的优化工作似乎比 C 编译器做得更好。

于 2015-06-24T15:05:24.163 回答
2

请注意,在线程环境中正确进行游戏搜索可能会非常痛苦(我已经尝试过了)。可以做到,但是从我不久前进行的一些文献搜索来看,很难从中获得任何速度提升。

于 2009-07-10T22:45:06.330 回答
2

我知道在大学的 AI 课程中谈到的一项改进是拥有庞大的终结技数据库。所以有一个预先计算好的游戏数据库,只剩下少量的数字。因此,如果您在搜索中达到近端定位,您将停止搜索并采用预先计算的值来改善您的搜索结果,例如您可以为重要/批评移动进行额外加深,而无需花费太多计算时间。我认为它还伴随着游戏后期状态的启发式变化,但我不是国际象棋玩家,所以我不知道游戏结束的动态。

于 2009-07-10T19:11:06.433 回答
2

这是一个相当老的问题,我只是在国际象棋上搜索问题,发现这个问题没有答案。好吧,它现在可能对您没有任何帮助,但可能对其他用户有帮助。

我没有看到空移动修剪,换位表..你在使用它们吗?他们会给你很大的推动...

让我大受鼓舞的一件事是最小化条件分支……很多事情都可以预先计算。寻找这样的机会。

大多数现代 PC 都有多个内核,因此将其设为多线程是一个好主意。您不一定需要为此使用 MDF(f)。

我不建议将您的代码移至位板。它的工作量太大了。尽管位板可以提升 64 位机器的性能。

最后也是最重要的一点,国际象棋文献主导了我们可能使用的任何优化。优化工作量太大。看看开源国际象棋引擎,特别是狡猾的和水果/托加。Fruit 最初是开源的。

于 2009-12-21T07:19:17.030 回答
2

迟到的答案,但这可能会帮助某人:

鉴于您提到的所有优化,1450 ELO 非常低。我的猜测是您的代码有问题。你是否:

  1. 编写了一个perft例程并通过一组位置运行它?所有测试都应该通过,所以您知道您的移动生成器没有错误。如果你没有这个,那么谈论 ELO 是没有意义的。

  2. 编写mirrorBoard例程并通过一组位置运行评估代码?正常位置和镜像位置的结果应该相同,否则您的评估中有错误。

  3. 你有哈希表(又名转置表)吗?如果没有,这是必须的。这将有助于搜索和排序动作,从而在速度上产生残酷的差异。

  4. 你如何实现移动排序?这链接回第 3 点。

  5. 你实现了 UCI 协议吗?你的move parsing功能正常吗?我的引擎中有这样的错误:

      /* Parses a uci move string and return a Board object */
      Board parseUCIMoves(String moves)// e2e4 c7c5 g1f3 ...{
          //...
          if (someMove.equals("e1g1") || someMove.equals("e1c1"))
              //apply proper castle
         //...
     }
    

有时在玩比赛时引擎会崩溃,我认为是 GUI 故障,因为所有性能测试都正常。我花了一周的时间才幸运地找到了这个错误。所以,测试一切。

对于 (1),您可以搜索深度 6 的每个位置。我使用具有 ~1000 个位置的文件。见这里https://chessprogramming.wikispaces.com/Perft

对于 (2),您只需要一个包含数百万个位置的文件(只是 FEN 字符串)。

鉴于以上所有因素和一个非常基本的评估功能(材料、棋子方桌、通过棋子、国王安全),它应该在 +-2000 ELO 下运行。

于 2016-11-14T18:48:22.657 回答
1

至于技巧,我知道在任何 eval 函数之前优化你的移动生成例程可以找到很大的收益。使该功能尽可能紧凑可以使您的节点/秒提高 10% 或更多。

如果您要转向位板,请在 rec.games.chess.computer 档案中挖掘 Robert Hyatts 博士关于 Crafty 的一些旧帖子(很确定他不再发帖了)。或者从他的 FTP中获取最新的副本并开始挖掘。不过,我很确定这对您来说将是一个重大转变。

于 2009-07-10T19:22:16.943 回答
0
  • 转置表
  • 打开书
  • 结束游戏桌基地
  • 改进的叶节点静态板评估
  • 原始速度的位板
于 2009-07-10T19:25:05.673 回答
0

简介和基准。理论上的优化很棒,但除非您衡量所做的每项更改对性能的影响,否则您将不知道您的工作是提高还是降低了最终代码的速度。

尝试限制对自己尝试不同算法的惩罚。使相互测试各种算法实现变得容易。ie 使构建代码的 PVS 版本和 NegaScout 版本变得容易。

寻找热点。重构。如有必要,在汇编中重写。重复。

于 2013-12-25T02:47:27.240 回答
-1

假设“历史启发式”涉及某种过去动作的数据库,那么学习算法不会给你更多,除非它与同一个玩家玩很多游戏。您可以通过对玩家进行分类并调整历史数据库中的移动选择来实现更多目标。

于 2009-07-10T16:17:58.827 回答
-2

自从我对任何国际象棋程序进行任何编程以来已经有很长时间了,但当时,位棋盘确实提供了真正的改进。除此之外,我不能给你太多建议。你只评估棋子的位置吗?一些关键部分的位置或移动性的一些(轻微)奖金可能是有序的。

我不确定你希望它学习什么类型的东西......

于 2009-07-10T19:01:45.033 回答