关于哪种语言速度执行速度最快,画了张图表,顺带吐槽

2021-05-16 05:48:27 +08:00
 LeeReamond

起因:昨天论坛老哥分享 Python 性能提升计划,其中一些涉及性能的讨论,27 楼老哥自己画了个图表看起来挺不错的。原贴链接

我个人是因为对 python 性能提升很感兴趣,对其他语言的语言性能比较也很感兴趣,有大版本更新的时候都会搞一些简单算法看看到底快了多少。这回看到这个老哥的图感觉挺不错的,该老哥称自己定期爬取 the benchmarks ganme 画出来的图,我感觉不如直接搞成一个自动维护项目,以后也省得手贱再自己动手跑了。

鼓捣了一下,大概思路就是基于 github 的分发特性,一个图片链接可以一直保持在最新数据,然后我在原版数据的基础上添加了两个 python 的 jit 解释器( pypy 和 pyston ),最后结果如下:

数据来源 https://benchmarksgame-team.pages.debian.net/benchmarksgame/index.html

=====================

几点说明:

1 、语言性能讨论一直是引战话题,我不是很理解为什么有人会在这个问题上被摸到 G 点,为了避免被喷,我先声明我这个不是严谨性能测试,只是基于开源跑分的爬虫并绘制图表而已。这个项目产生的原因是希望为需要的人提供一些定性结论(或者是定性结论的参考,毕竟它有可能不准),而不是严谨的定量结论。

2 、以前虽然跟别人讨论经常引用 the benchmarks game 的结论,但从来没仔细研究过这个网站。这次顺带仔细看了一下算法,我觉得它这些设计有几个可能本身也并不严谨。本身对比语言性能就是很难设计的一件事,理解。

3 、我在做这张图表的时候遇到一个统计学问题,由于一张图里要汇总多项测试的平均水平,一个最简单的想法是求平均耗时。但是需要考虑到一种极端情况就是,假设大部分测试都以非常短的耗时(比如 10 秒以内)完成,而个别测试极端情况的项目的总体时间消耗都很高(比如超过 600 秒),那么如果单纯做加和的话,整体的平均值会很大程度上取决于这个极端情况的结果,而削减了其他项目的影响力,为了避免这种情况发生,需要对数据加权。

我的方案是采用了如下图所示的运算得到最终结果,其中σ是方差,L 是常数,V 代表输出值。

大体思路就是,当一个项目整体测试时间较长的时候,我们会略微削弱这个项目的权重,让他不要影响那么大。同时考虑到如果用离散度描述整体测试时间的跨度,方差的比例差可能是一个非常大的数,所以需要进行某种压限,以确保权重最低的项目不会比权重最高的项目低出太多。算法是拍脑袋想的,有兴趣的欢迎看源码,或提意见。

4 、the benchmarks game 这个网站算是比较熟了,隔一段时间总会看到,但是说实话这次跑出来比例挺多反常识的,大概有以下几点:

我后面手动检查了一下数据,应该不是我写错了,确实测试结果加权平均算下来就是这样的,可能是由于这个跑分本身也没那么严谨,或者是特定语言在一些大量使用场景(比如正则)有让效率接近原生级的优化所导致的。

6168 次点击
所在节点    分享创造
44 条回复
shyrock
2021-05-17 18:15:08 +08:00
@046569 #12 2.4 的意思是 24 倍?我懵了
monkeyNik
2021-05-18 18:24:54 +08:00
@pkookp8 只能说些垃圾代码的太多,拖了后腿
lesismal
2021-05-18 18:28:49 +08:00
“Java 比 Go 快,而且领先幅度不小。”
—— 这是错觉。

全部对比应该是在这里吧:
https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/go.html

这是 go 和 java 对比的,大部分测试项 cpu 消耗其实各有千秋,最后两个测试项差距确实很大,但是,稍微修改下再对比:

以最后一项的 binary-trees 为例( go 耗时 12.80s ,java 耗时 2.48s ):

go 代码修改:
func inner(depth, iterations uint32) string {
chk := uint32(0)
tree := bottomUpTree(depth) // 这里从循环内移到了循环外
for i := uint32(0); i < iterations; i++ {
chk += itemCheck(tree)
}
return fmt.Sprintf("%d\t trees of depth %d\t check: %d",
iterations, depth, chk)
}

java 代码修改:
for (int d = MIN_DEPTH; d <= maxDepth; d += 2) {
final int depth = d;
EXECUTOR_SERVICE.execute(() -> {
int check = 0;

final int iterations = 1 << (maxDepth - depth + MIN_DEPTH);
final TreeNode treeNode1 = bottomUpTree(depth); // 这里从循环内移到了循环外
for (int i = 1; i <= iterations; ++i) {
check += treeNode1.itemCheck();
}
results[(depth - MIN_DEPTH) / 2] =
iterations + "\t trees of depth " + depth + "\t check: " + check;
});
}

我对 java 不熟,猜测 java 的编译器对 for 循环内局部对象甚至 tree 的构造过程做了复用优化

因为这种简单逻辑内的遍历,更成熟的老龄编译器可能会做更针对的优化,C++比 C 性能强主要就在于现代 C++编译器的优化,并且实际的业务场景,很少有需要这种频繁创建这样深度和节点数量的临时对象,几乎不会遇到这种小代码段级别的优化在实际业务中发挥太大性能优势,所以把遍历的部分构造放在外层时只对比 check 消耗时,性能差不多、在我机器上 go 略好。

语言、编译器有一个漫长的成长阶段,go 会越来越强的
lesismal
2021-05-18 18:35:12 +08:00
“Go 和 javascript 速度居然差不多。”
—— 这个没关系,毕竟 js 逻辑单线程,对多核心的利用能力肯定是不如 go 的,有人可能说 js 可以开多进程,但是 go 相比多进程还有内存共享、无需通信的优势,在面对更复杂业务功能时还是要强于 js 。所以单就服务端领域,抛开什么社区轮子或者说用 node 弱全栈配合写前端的优势,纯做服务器,go 还是比 js 强大太多,否则 node 爹当年也不会在熟悉了 go 之后感慨 node 不适合做服务器、而是适合辅助前端开发

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/777179

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX