我们以5x5井字游戏为例。 让我们说这是我的AI。 然后,
以下是我的问题:
使用25个线程是否有效?使用25个线程意味着什么?
它快25倍(很可能不是)?它取决于什么?当然,在计算机上,但我如何根据计算机的资源知道可以使用多少线程?
我的想法好吗?感谢。
答案 0 :(得分:12)
对于典型的计算绑定应用程序,一个好的经验法则是使用与硬件核心(或超线程)一样多的线程。使用比核心更多的线程不会使您的应用程序更快。相反,它将导致您的应用程序使用比必要更多的内存。每个线程通常具有0.5到1M字节的堆栈......具体取决于您的硬件和Java版本。如果您创建了太多线程,额外的内存使用将导致显着的性能损失;即更多线程=>慢节目!
另一件需要考虑的事情是,在典型的JVM上创建Java线程的成本很高。因此,除非一个线程做了足够的工作(在其生命周期内),否则你可能会花费更多的时间来创建线程,而不是通过在计算中使用多个内核来获得线程。
最后,你可能会发现作品并没有在所有线程上均匀分布,具体取决于你的minmax算法......以及游戏状态。
如果我试图实现这一点,我首先将其实现为单线程应用程序,然后:
当且仅当它需要更快时,我会检查代码并(如果需要)添加一些监视,以查看如何将计算分解为足够大的块以便并行执行。
最后,我将使用这些结果来设计和实现多线程版本。
我还会考虑替代方案......比如使用Java 7 fork / join而不是线程。
回答您的直接问题:
使用25个线程是否有效?
可能不是。只有拥有那么多内核才会有效(不太可能!)。即使这样,如果你通过并行运行获得更多线程而不是因为与线程相关的开销而损失,那么你只能通过使用大量线程获得良好的加速。 (换句话说,它取决于你使用这些线程的效率。)
使用25个线程意味着什么?
我认为你意味着你已经创建并启动了25个线程,无论是显式还是使用现有的线程池实现。
但最重要的是,如果您有(比方说)4个核心,那么最多这25个线程中的4个可以同时执行。其他线程将等待......
它快25倍(很可能不是)吗?它取决于什么?当然,在计算机上,但我怎么知道根据计算机的资源可以使用多少个线程呢?
限制性能的主要因素是核心数量。见上文。
如果我使用太多线程会发生什么(我猜不出......)?
线程太多意味着您使用更多内存,这会因为内存带宽竞争,物理内存页面竞争,额外垃圾回收而导致应用程序运行速度变慢。这些因素依赖于应用和平台,难以量化;即预测或衡量。
根据应用程序的性质(即精确地如何实现算法),太多线程可能导致额外的锁争用和线程上下文切换。这也会使你的应用程序变慢。
如果没有看到您的实际代码,就无法预测会发生什么。但核心数量可以为您提供可能的加速比的理论上限。如果你有4个核心,那么多线程的速度不会超过4倍。
答案 1 :(得分:3)
因此,给出的线程答案是可以的,但在我看来,他们忽略了minimax搜索的alpha-beta修剪功能。
如果从当前位置为每个“下一步移动”启动一个线程,那么让这些线程相互通信是正确的,并且很难写。但是,如果他们不能互相交谈,那么你就不会得到来自alpha-beta修剪的深度提升,直到一个级别进一步下降。
这将违背结果的效率。
对于改进计算时间的一般情况,最好的情况往往是每个核心1个线程,如果它们都是相似的时间(例如矩阵乘法),或者具有“set”,则可以将任务简单地分配给线程。任务,每当完成当前任务时,每个线程抓住下一个未启动的任务。 (这有一些锁定任务,但如果它们与分辨率成本相比较小则非常有效)。
因此,对于4核系统和~25个自然任务,您可以希望在3.5-4x范围内加速。 (你会做4个并行~5次,然后完成混乱)。但是,在极小极大情况下,你已经失去了alpha-beta修剪方面,据我所知,估计会将“有效宽度”从N减少到大约sqrt(N)。对于~25的情况,这意味着有效的分支因子为5.这意味着使用4个核心并且跳过第一级的修剪可能实际上伤害了你。
那么,离开我们的地方呢?
答案 2 :(得分:3)
正如我所有的朋友所说的那样,使用与机器一样多的线程容量。
但是通过添加它们也应该改进算法。
例如,在5x5 tic tac toe中,两者都将得到12或13个动作。因此,nCr(组合方程式)基数为25C12 = 5,200,300。所以现在你已经减少了线程数量,现在进行最佳选择你有最好的方法找到最佳解决方案只有12(赢得位置)& 12失去最差的条件所有其他都是绘制条件。所以你现在可以做的就是从线程&中检查这12个条件。留下额外的组合与你需要创建12的计算! * 12没有与25相比非常低的线程!因此您的线程数量会减少,您可以进一步考虑减少线程数量。
当你的动作更加充实时您可以使用alpha-beta修剪更多,以便您也可以改进算法。
答案 3 :(得分:1)
如果您正在使用线程,那么为了防止内存浪费,只需将它们用于mini-max的第一次调用,然后组合线程的结果以获得输出。如果你使用25个线程或者某个数字这么大,这是一个浪费,因为可用内核的数量要小于那么,所以你可以做的就是在不同的状态下一次只安排相当于可用内核的线程,并将所有结果组合在一起结束。
这是伪代码: -
int miniMax(State,Player,depth) {
// normal minimax code
}
State ParaMiniMax(State,Player) {
int totalThreads = Runtime.getRuntime().availableProcessors());
NextStates = getNextStates(State);
while(NextStates.size()>0) {
k = totalThreads;
while(k>0 && NextStates.size>0) {
//Schedule thread with nextState. with run calling miniMax with other player
//Store (score,state) in Result List
k--;
NextStates.removeTop();
}
wait(); // waits for threads to complete
}
if(player==max) {
return(maxScore(Result).State);
}
else return(minScore(Result).State);
}
答案 4 :(得分:0)
您应该只使用与机器具有的核心数相等的线程数。将任务调度到这些线程上是另一回事。
答案 5 :(得分:0)
考虑问题的对称性。实际上只有非常有限的“独特”初始移动 - 其余的是相同的但是反射或旋转(因此具有相同的战略价值)。 5x5板的独特举措是:
xxx..
.xx..
..x..
.....
.....
或者仅仅是6个初始动作。 Bam - 你只是在没有线程的情况下将复杂度降低了4倍。
正如其他人所说,除了单个线程花费时间“等待” - 输入,内存访问和其他结果之外,线程数量超过内核通常无助于加速。可能是六个线程将是一个很好的起点。
为了说服你对称性,我用相同的数字标记相同的位置 - 如果你同意的话,
12321
24542
35653
24542
12321
当您旋转90度的任意倍数或反映对角线或左右,上下时,这是相同的。
PS我意识到这并没有真正回答你提出的问题,但我相信它直接解决了你的基本问题 - “我如何有效地解决5x5井字游戏问题”。因此,如果你选择不同的答案,我不会感到沮丧,但我希望你能牢记我的建议。