为什么这个GoLang解决方案比等效的Java解决方案更快?

2022-09-01 03:54:46

最近在工作中,我们正在玩IBM提出的以下测验问题 https://www.research.ibm.com/haifa/ponderthis/challenges/May2015.html

经过一番努力,我和我的一位同事已经找到了两种解决方案,一种是在GoLang https://gist.github.com/walesey/e2427c28a859c4f7bc920c9af2858492#file-main-go-L57 中,另一种是在Java中,https://gist.github.com/boyter/42df7f203c0932e37980f7974c017ec5#file-puzzle-java-L63 性能关键方法,即Java中的playGames和GoLang中的game(两者都在上面链接)。

Go程序几乎是Java程序的字面副本,但它的运行时间约为6秒,而Java程序的运行时间约为26秒(在我的本地计算机上)。类似的数字在其他几台机器上复制,Go程序的速度提高了约5倍。

Go程序是使用1.7.5编译的,Java使用版本1.8.0_65编译的,两者都运行在macOS Sierra 10.12.3上,在2013年末的retina Macbook Pro上,配备2.6GHz i5 CPU。

为什么Go程序比Java程序快5倍,而大多数基准测试表明Java应该使用相同的运行时?它只是循环中的基本数学,因此它们似乎应该同时运行。对于JVM的开始时间,我可以理解一秒钟左右,但这似乎不对。

这两个程序几乎使用相同的循环。将为每个起始金额创建并迭代所有可能的游戏结果排列。似乎对于主循环中任意数量的循环操作,Go 都在 Java 周围运行环。

我知道这是一个“微”基准测试,但我想知道为什么Go代码的性能远远超过Java代码。只是 Go for simple 循环/数学更有效,因此更快吗?它是否能够展开循环(尽管这似乎不太可能产生如此巨大的差异)?

如果不是,您应该如何构建Java程序以从简单的循环和数学运算中获得最大性能?

编辑 - 感谢Dolda2000,我修改了Java版本。它现在的速度与GoLang版本大致相同。事实上,问题在于游戏的创建导致Java版本必须模拟更多游戏才能确定游戏是否足够长。随着这些变化,它现在在大约6秒内运行,并恢复了我对Java的信心。

更新 - 这是一篇扩展的文章,更详细地讨论了这个问题的背景。


答案 1

事实证明,你的程序并不像你认为的那样平等。我测试了他们,看看他们模拟了多少场比赛(即个人投注回合),而围棋版模拟了1 612 629 805场比赛,Java版本模拟了12 323 903 502场比赛,几乎多了一个数量级。

在我的机器上,关闭多线程以获得更可预测的结果,Java程序在75秒左右,Go程序在12.5秒内打卡。与总运行时相匹配,似乎每个模拟游戏的Java程序实际上略快,约为6.1 ns,而Go程序为7.8 ns。

不过,还不确定为什么他们模拟了如此不同数量的游戏。也许Go版本生成回合的方式只是碰巧找到了更快的终止。

编辑:实际上,最后的猜测很有道理。Go版本从调制游戏的初始回合开始,而Java版本从调制游戏的最后几轮开始(换句话说,将回合列表视为增加的11位基数3数字的列表,Go版本是小端,而Java版本是大端, 可以这么说),所以Java版本将不得不通过更多相同的开始来模拟,以获得终止的变化。我还没有试图验证这个假设,但我足够确定,我不觉得有必要。


答案 2

推荐