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

huangapple go评论92阅读模式
英文:

Why is this GoLang solution faster then the equivalent Java Solution?

问题

最近在工作中,我们在玩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启动时间大约需要1秒左右,但这个差距似乎太大了。

两个程序都使用了几乎相同的循环。对于每个起始金额,都会创建并迭代所有可能的游戏结果的排列组合。看起来,对于主循环中的任何循环操作次数,Go都比Java更快。

我知道这只是一个“微型”基准测试,但我想知道为什么Go代码比Java代码性能要好得多。是因为对于简单的循环和数学运算,Go更高效,因此更快吗?它可能会展开循环吗(尽管这似乎不太可能导致如此大的差异)?

如果不是这样,应该如何构建一个Java程序,以在简单的循环和数学运算中获得最佳性能?

编辑 - 感谢Dolda2000的建议,我修改了Java版本。现在它的速度与GoLang版本差不多了。实际上,问题出在了创建游戏的方式上,导致Java版本需要模拟更多的游戏来确定游戏是否进行到足够长的时间。通过这些改变,现在它的运行时间约为6秒,并且恢复了我对Java的信心。

更新 - 这里有一篇扩展的文章,进一步详细讨论了这个问题的背景。

英文:

Recently at work we were playing around with the following quiz question asked by IBM https://www.research.ibm.com/haifa/ponderthis/challenges/May2015.html

After a bit of effort a colleague and I have arrived at two solutions, one in GoLang https://gist.github.com/walesey/e2427c28a859c4f7bc920c9af2858492#file-main-go-L57 and the other in Java https://gist.github.com/boyter/42df7f203c0932e37980f7974c017ec5#file-puzzle-java-L63 with the performance critical method for both being playGames in Java and game in GoLang (both linked in above).

The Go program is almost a literal copy of the Java one, and yet its runtime is ~6 seconds whereas the Java one is about ~26 seconds (on my local machine). Similar numbers were replicated on a few other machines with the Go program being about ~5x faster.

The Go program is compiled using 1.7.5 and Java using version 1.8.0_65 both running on macOS Sierra 10.12.3 on a late 2013 retina Macbook Pro with 2.6GHz i5 CPU.

Why is it that the Go program is 5x faster then the Java one when most benchmarks indicate that Java should about the same runtime? It is just basic math in a loop so it seems that they should run about the same time. I could understand a second or so for the JVM start time, but this seems off.

Both programs use pretty much the same loop. All of the possible permutations of game results are created and iterated over for each starting amount of money. It just seems that for any number of looping operations in the main loop that Go is running rings around Java.

I understand that this is a "micro" benchmark, but I am wondering why exactly the Go code is massively outperforming the Java code. Is it just that Go for simple loops/math is more efficient and hence faster? Is it able to unroll the loop perhaps (although this seems unlikely to produce such a massive difference)?

If not how should you structure a Java program to get the most performance out of a simple loop and math operation?

EDIT - Thanks to Dolda2000 I have modified the Java version. It is now about the same speed as the GoLang version. Indeed the issue was the the games were created causing the Java version to have to simulate more games to determine if the game went long enough. With the changes it is running in about ~6 seconds now and has restored my faith in Java.

Update - Here is an expanded essay which discusses the background of this question in further detail.

答案1

得分: 48

原来如此,你的程序并不像你认为的那样相等。我对它们进行了测试,看看它们模拟了多少场游戏(即个别的投注回合),结果发现Go版本模拟了1,612,629,805场游戏,而Java版本模拟了12,323,903,502场游戏,几乎是一个数量级的差异。

在我的机器上,为了得到更可预测的结果,关闭了多线程,Java程序运行时间约为75秒,而Go程序运行时间为12.5秒。将这些与总运行时间进行对比,似乎Java程序实际上每个模拟游戏的速度略快,约为6.1纳秒,而Go程序为7.8纳秒。

不确定它们为什么会模拟如此不同数量的游戏。也许Go版本生成回合的方式恰好能够更快地终止。

编辑:实际上,最后的猜测非常有道理。Go版本首先调节游戏的初始回合,而Java版本首先调节游戏的最后回合(换句话说,将回合列表视为递增的11位三进制数列表,Go版本是小端,而Java版本是大端,可以这么说),因此Java版本需要模拟更多相同的起始状态才能达到终止的变化。我还没有尝试验证这个假设,但我对它足够有信心,不觉得有必要验证。

英文:

As it turns out, your programs aren't as equal as you believe them to be. I instrumented them to see how many games (that is, individual betting rounds) they simulated, and while the Go version simulated 1 612 629 805 games, the Java version simulated 12 323 903 502 games, almost an order of magnitude more.

On my machine, turning off multithreading for more predictable results, the Java program clocked in on around 75 seconds, and the Go program on 12.5 seconds. Matching that against the total runtime, it appears that the Java program is actually slightly faster per simulated game, at about 6.1 ns, as compared to 7.8 ns for the Go program.

Not sure yet why they simulate such vastly different numbers of games, though. Perhaps the way the Go version generates the rounds simply happens to find a lot more quicker terminations.

EDIT: Actually, that last guess makes a lot of sense. The Go version starts off by modulating the initial rounds of a game, while the Java version starts off by modulating the last rounds of a game (in other words, looking at the list of rounds as a list of increasing 11-digit base-3 numbers, the Go version is little-endian, while the Java version is big-endian, so to speak), so the Java version will have to simulate through a lot more identical starts to get to the variations that terminate. I haven't tried to verify this hypothesis, but I'm sure enough about it that I don't feel the need to.

huangapple
  • 本文由 发表于 2017年3月29日 08:17:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/43082115.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定