MRI Ruby 和 jRuby 之间的性能差异
Performance difference between MRI Ruby and jRuby
在做一些基准测试来回答 关于连接数组的最快方法的问题时,令我惊讶的是,当我使用 jRuby 进行相同的基准测试时,测试速度要慢得多。
这是否意味着关于 jRuby 比 MRI Ruby 快的旧慢板已经消失了?或者这是关于数组在 jRuby 中的处理方式?
这里是 MRI Ruby 2.3.0 和 jRuby 9.1.2.0 中的基准和结果
运行 在 64 位 Windows 7 机器上,所有 4 个处理器都忙于 50-60%,使用的内存为 ± 5.5GB。 jRuby 必须以参数 -J-Xmx1500M
启动以提供足够的堆 space。由于堆栈级别太深,我不得不使用 push 删除测试,并且还删除了最慢的方法以免测试时间过长。使用 Jave 运行时间:1.7.0_21
require 'Benchmark'
N = 100
class Array
def concat_all
self.reduce([], :+)
end
end
# small arrays
a = (1..10).to_a
b = (11..20).to_a
c = (21..30).to_a
Benchmark.bm do |r|
r.report('plus ') { N.times { a + b + c }}
r.report('concat ') { N.times { [].concat(a).concat(b).concat(c) }}
r.report('splash ') { N.times {[*a, *b, *c]} }
r.report('concat_all ') { N.times { [a, b, c].concat_all }}
r.report('flat_map ') { N.times {[a, b, c].flat_map(&:itself)} }
end
#large arrays
a = (1..10_000_000).to_a
b = (10_000_001..20_000_000).to_a
c = (20_000_001..30_000_000).to_a
Benchmark.bm do |r|
r.report('plus ') { N.times { a + b + c }}
r.report('concat ') { N.times { [].concat(a).concat(b).concat(c) }}
r.report('splash ') { N.times {[*a, *b, *c]} }
r.report('concat_all ') { N.times { [a, b, c].concat_all }}
r.report('flat_map ') { N.times {[a, b, c].flat_map(&:itself)} }
end
此问题与使用的不同方法无关,请参阅原始问题。
在这两种情况下,MRI 都快 7 倍!
有人可以解释我为什么吗?
我也很好奇其他实现如何做,比如 RBX (Rubinius)
C:\Users\...>d:\jruby\bin\jruby -J-Xmx1500M concat3.rb
user system total real
plus 0.000000 0.000000 0.000000 ( 0.000946)
concat 0.000000 0.000000 0.000000 ( 0.001436)
splash 0.000000 0.000000 0.000000 ( 0.001456)
concat_all 0.000000 0.000000 0.000000 ( 0.002177)
flat_map 0.010000 0.000000 0.010000 ( 0.003179)
user system total real
plus 140.166000 0.000000 140.166000 (140.158687)
concat 143.475000 0.000000 143.475000 (143.473786)
splash 139.408000 0.000000 139.408000 (139.406671)
concat_all 144.475000 0.000000 144.475000 (144.474436)
flat_map143.519000 0.000000 143.519000 (143.517636)
C:\Users\...>ruby concat3.rb
user system total real
plus 0.000000 0.000000 0.000000 ( 0.000074)
concat 0.000000 0.000000 0.000000 ( 0.000065)
splash 0.000000 0.000000 0.000000 ( 0.000098)
concat_all 0.000000 0.000000 0.000000 ( 0.000141)
flat_map 0.000000 0.000000 0.000000 ( 0.000122)
user system total real
plus 15.226000 6.723000 21.949000 ( 21.958854)
concat 11.700000 9.142000 20.842000 ( 20.928087)
splash 21.247000 12.589000 33.836000 ( 33.933170)
concat_all 14.508000 8.315000 22.823000 ( 22.871641)
flat_map 11.170000 8.923000 20.093000 ( 20.170945)
一般规则是(如评论中所述)JRuby/JVM 需要热身。
通常 bmbm
很合适,虽然 TIMES=1000
应该增加(至少对于小数组的情况),而且 1.5G 可能不足以实现 JRuby 的最佳性能(注意到相当大的数字从 -Xmx2g 变为 -Xmx3g)。这是结果:
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]
$ ruby concat3.rb
Rehearsal -----------------------------------------------
plus 0.000000 0.000000 0.000000 ( 0.000076)
concat 0.000000 0.000000 0.000000 ( 0.000070)
splash 0.000000 0.000000 0.000000 ( 0.000099)
concat_all 0.000000 0.000000 0.000000 ( 0.000136)
flat_map 0.000000 0.000000 0.000000 ( 0.000138)
-------------------------------------- total: 0.000000sec
user system total real
plus 0.000000 0.000000 0.000000 ( 0.000051)
concat 0.000000 0.000000 0.000000 ( 0.000059)
splash 0.000000 0.000000 0.000000 ( 0.000083)
concat_all 0.000000 0.000000 0.000000 ( 0.000120)
flat_map 0.000000 0.000000 0.000000 ( 0.000173)
Rehearsal -----------------------------------------------
plus 43.040000 3.320000 46.360000 ( 46.351004)
concat 15.080000 3.870000 18.950000 ( 19.228059)
splash 49.680000 4.820000 54.500000 ( 54.587707)
concat_all 51.840000 5.260000 57.100000 ( 57.114867)
flat_map 17.380000 5.340000 22.720000 ( 22.716987)
------------------------------------ total: 199.630000sec
user system total real
plus 42.880000 3.600000 46.480000 ( 46.506013)
concat 17.230000 5.290000 22.520000 ( 22.890809)
splash 60.300000 7.480000 67.780000 ( 67.878534)
concat_all 54.910000 6.480000 61.390000 ( 61.404383)
flat_map 17.310000 5.570000 22.880000 ( 23.223789)
...
jruby 9.1.6.0 (2.3.1) 2016-11-09 0150a76 Java HotSpot(TM) 64-Bit Server VM 25.112-b15 on 1.8.0_112-b15 +jit [linux-x86_64]
$ jruby -J-Xmx3g concat3.rb
Rehearsal -----------------------------------------------
plus 0.010000 0.000000 0.010000 ( 0.001445)
concat 0.000000 0.000000 0.000000 ( 0.002534)
splash 0.000000 0.000000 0.000000 ( 0.001791)
concat_all 0.000000 0.000000 0.000000 ( 0.002513)
flat_map 0.010000 0.000000 0.010000 ( 0.007088)
-------------------------------------- total: 0.020000sec
user system total real
plus 0.010000 0.000000 0.010000 ( 0.002700)
concat 0.000000 0.000000 0.000000 ( 0.001085)
splash 0.000000 0.000000 0.000000 ( 0.001569)
concat_all 0.000000 0.000000 0.000000 ( 0.003052)
flat_map 0.000000 0.000000 0.000000 ( 0.002252)
Rehearsal -----------------------------------------------
plus 32.410000 0.670000 33.080000 ( 17.385688)
concat 18.610000 0.060000 18.670000 ( 11.206419)
splash 57.770000 0.330000 58.100000 ( 25.366032)
concat_all 19.100000 0.030000 19.130000 ( 13.747319)
flat_map 16.160000 0.040000 16.200000 ( 10.534130)
------------------------------------ total: 145.180000sec
user system total real
plus 16.060000 0.040000 16.100000 ( 11.737483)
concat 15.950000 0.030000 15.980000 ( 10.480468)
splash 47.870000 0.130000 48.000000 ( 22.668069)
concat_all 19.150000 0.030000 19.180000 ( 13.934314)
flat_map 16.850000 0.020000 16.870000 ( 10.862716)
...所以它看起来恰恰相反 - MRI 2.3 比 JRuby 9.1
慢 2-5 倍
cat concat3.rb
require 'benchmark'
N = (ENV['TIMES'] || 100).to_i
class Array
def concat_all
self.reduce([], :+)
end
end
# small arrays
a = (1..10).to_a
b = (11..20).to_a
c = (21..30).to_a
Benchmark.bmbm do |r|
r.report('plus ') { N.times { a + b + c }}
r.report('concat ') { N.times { [].concat(a).concat(b).concat(c) }}
r.report('splash ') { N.times {[*a, *b, *c]} }
r.report('concat_all ') { N.times { [a, b, c].concat_all }}
r.report('flat_map ') { N.times {[a, b, c].flat_map(&:itself)} }
end
#large arrays
a = (1..10_000_000).to_a
b = (10_000_001..20_000_000).to_a
c = (20_000_001..30_000_000).to_a
Benchmark.bmbm do |r|
r.report('plus ') { N.times { a + b + c }}
r.report('concat ') { N.times { [].concat(a).concat(b).concat(c) }}
r.report('splash ') { N.times {[*a, *b, *c]} }
r.report('concat_all ') { N.times { [a, b, c].concat_all }}
r.report('flat_map ') { N.times {[a, b, c].flat_map(&:itself)} }
end
我从这些评论和答案中学到了什么以及我后来自己做的测试..
- OS 可能有所不同,我希望得到更多答案
在不同的情况下所以我只是猜测
- 最快的方法在 运行time、MRI 或 jRuby、64 位的 32、JRE 之间有所不同,因此声称该方法比那个更好
另一个很难,在我的系统上,plus 方法是最快的
几乎所有情况,但我没有像 kares
那样使用 Java HotSpot
- 在 64 位 jRuby 中,您可以指定比 32 位高得多的堆(我的系统上为 1.5G),在 64 位中,我可以使用比我拥有的更多的堆
内存(某个地方的错误?)
- 更高的堆可以加快使用大量内存的操作,就像我使用的巨大数组一样
- 使用最新的Java运行时间,速度更好
- jRuby需要预热,一个方法在编译前需要运行多次,所以使用不同重复值的.bm和.bmbm来
找到边距
- 有时 MRI 速度更快,但使用正确的参数和预热 jRuby 在我的系统上对于这个特定的速度是 3 到 3.5 倍
测试
最后,与 JVM 的加载一起使 MRI 更适合短的即席脚本,jRuby更适合进程饥饿,较长的 运行ning 过程,方法经常重复,所以 j Ruby 更适合 运行 宁服务器和服务。
我所看到的证实了:对长时间或重复的过程进行自己的基准测试。
与早期版本相比,这两种实现在速度上都取得了很大的改进,我们不要忘记:Ruby 可能是一个较慢的 运行ner 但一个更快的开发人员,如果你比较一些额外硬件的成本与一些额外的开发人员...
感谢所有评论者和凯伦的专业知识。
编辑
出于好奇,我 运行 也在 docker 容器中使用 Rubinius 进行了测试(我在 Windows),rubinius 3.69 (2.3.1 a57071c6 2016-11-17 3.8.0) [x86_64-linux-gnu]
只有 concat 和 flat_map 与 MRI 相当,我想知道这些方法是否在 C 中,其余的在纯 Ruby..
Rehearsal -----------------------------------------------
plus 0.000000 0.000000 0.000000 ( 0.000742)
concat 0.000000 0.000000 0.000000 ( 0.000093)
splash 0.000000 0.000000 0.000000 ( 0.000619)
concat_all 0.000000 0.000000 0.000000 ( 0.001357)
flat_map 0.000000 0.000000 0.000000 ( 0.001536)
-------------------------------------- total: 0.000000sec
user system total real
plus 0.000000 0.000000 0.000000 ( 0.000589)
concat 0.000000 0.000000 0.000000 ( 0.000084)
splash 0.000000 0.000000 0.000000 ( 0.000596)
concat_all 0.000000 0.000000 0.000000 ( 0.001679)
flat_map 0.000000 0.000000 0.000000 ( 0.001568)
Rehearsal -----------------------------------------------
plus 68.770000 63.320000 132.090000 (265.589506)
concat 20.300000 2.810000 23.110000 ( 23.662007)
splash 79.310000 74.090000 153.400000 (305.013934)
concat_all 83.130000 100.580000 183.710000 (378.988638)
flat_map 20.680000 0.960000 21.640000 ( 21.769550)
------------------------------------ total: 513.950000sec
user system total real
plus 65.310000 70.300000 135.610000 (273.799215)
concat 20.050000 0.610000 20.660000 ( 21.163930)
splash 79.360000 80.000000 159.360000 (316.366122)
concat_all 84.980000 99.880000 184.860000 (383.870653)
flat_map 20.940000 1.760000 22.700000 ( 22.760643)
在做一些基准测试来回答
这是否意味着关于 jRuby 比 MRI Ruby 快的旧慢板已经消失了?或者这是关于数组在 jRuby 中的处理方式?
这里是 MRI Ruby 2.3.0 和 jRuby 9.1.2.0 中的基准和结果
运行 在 64 位 Windows 7 机器上,所有 4 个处理器都忙于 50-60%,使用的内存为 ± 5.5GB。 jRuby 必须以参数 -J-Xmx1500M
启动以提供足够的堆 space。由于堆栈级别太深,我不得不使用 push 删除测试,并且还删除了最慢的方法以免测试时间过长。使用 Jave 运行时间:1.7.0_21
require 'Benchmark'
N = 100
class Array
def concat_all
self.reduce([], :+)
end
end
# small arrays
a = (1..10).to_a
b = (11..20).to_a
c = (21..30).to_a
Benchmark.bm do |r|
r.report('plus ') { N.times { a + b + c }}
r.report('concat ') { N.times { [].concat(a).concat(b).concat(c) }}
r.report('splash ') { N.times {[*a, *b, *c]} }
r.report('concat_all ') { N.times { [a, b, c].concat_all }}
r.report('flat_map ') { N.times {[a, b, c].flat_map(&:itself)} }
end
#large arrays
a = (1..10_000_000).to_a
b = (10_000_001..20_000_000).to_a
c = (20_000_001..30_000_000).to_a
Benchmark.bm do |r|
r.report('plus ') { N.times { a + b + c }}
r.report('concat ') { N.times { [].concat(a).concat(b).concat(c) }}
r.report('splash ') { N.times {[*a, *b, *c]} }
r.report('concat_all ') { N.times { [a, b, c].concat_all }}
r.report('flat_map ') { N.times {[a, b, c].flat_map(&:itself)} }
end
此问题与使用的不同方法无关,请参阅原始问题。 在这两种情况下,MRI 都快 7 倍! 有人可以解释我为什么吗? 我也很好奇其他实现如何做,比如 RBX (Rubinius)
C:\Users\...>d:\jruby\bin\jruby -J-Xmx1500M concat3.rb
user system total real
plus 0.000000 0.000000 0.000000 ( 0.000946)
concat 0.000000 0.000000 0.000000 ( 0.001436)
splash 0.000000 0.000000 0.000000 ( 0.001456)
concat_all 0.000000 0.000000 0.000000 ( 0.002177)
flat_map 0.010000 0.000000 0.010000 ( 0.003179)
user system total real
plus 140.166000 0.000000 140.166000 (140.158687)
concat 143.475000 0.000000 143.475000 (143.473786)
splash 139.408000 0.000000 139.408000 (139.406671)
concat_all 144.475000 0.000000 144.475000 (144.474436)
flat_map143.519000 0.000000 143.519000 (143.517636)
C:\Users\...>ruby concat3.rb
user system total real
plus 0.000000 0.000000 0.000000 ( 0.000074)
concat 0.000000 0.000000 0.000000 ( 0.000065)
splash 0.000000 0.000000 0.000000 ( 0.000098)
concat_all 0.000000 0.000000 0.000000 ( 0.000141)
flat_map 0.000000 0.000000 0.000000 ( 0.000122)
user system total real
plus 15.226000 6.723000 21.949000 ( 21.958854)
concat 11.700000 9.142000 20.842000 ( 20.928087)
splash 21.247000 12.589000 33.836000 ( 33.933170)
concat_all 14.508000 8.315000 22.823000 ( 22.871641)
flat_map 11.170000 8.923000 20.093000 ( 20.170945)
一般规则是(如评论中所述)JRuby/JVM 需要热身。
通常 bmbm
很合适,虽然 TIMES=1000
应该增加(至少对于小数组的情况),而且 1.5G 可能不足以实现 JRuby 的最佳性能(注意到相当大的数字从 -Xmx2g 变为 -Xmx3g)。这是结果:
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]
$ ruby concat3.rb
Rehearsal -----------------------------------------------
plus 0.000000 0.000000 0.000000 ( 0.000076)
concat 0.000000 0.000000 0.000000 ( 0.000070)
splash 0.000000 0.000000 0.000000 ( 0.000099)
concat_all 0.000000 0.000000 0.000000 ( 0.000136)
flat_map 0.000000 0.000000 0.000000 ( 0.000138)
-------------------------------------- total: 0.000000sec
user system total real
plus 0.000000 0.000000 0.000000 ( 0.000051)
concat 0.000000 0.000000 0.000000 ( 0.000059)
splash 0.000000 0.000000 0.000000 ( 0.000083)
concat_all 0.000000 0.000000 0.000000 ( 0.000120)
flat_map 0.000000 0.000000 0.000000 ( 0.000173)
Rehearsal -----------------------------------------------
plus 43.040000 3.320000 46.360000 ( 46.351004)
concat 15.080000 3.870000 18.950000 ( 19.228059)
splash 49.680000 4.820000 54.500000 ( 54.587707)
concat_all 51.840000 5.260000 57.100000 ( 57.114867)
flat_map 17.380000 5.340000 22.720000 ( 22.716987)
------------------------------------ total: 199.630000sec
user system total real
plus 42.880000 3.600000 46.480000 ( 46.506013)
concat 17.230000 5.290000 22.520000 ( 22.890809)
splash 60.300000 7.480000 67.780000 ( 67.878534)
concat_all 54.910000 6.480000 61.390000 ( 61.404383)
flat_map 17.310000 5.570000 22.880000 ( 23.223789)
...
jruby 9.1.6.0 (2.3.1) 2016-11-09 0150a76 Java HotSpot(TM) 64-Bit Server VM 25.112-b15 on 1.8.0_112-b15 +jit [linux-x86_64]
$ jruby -J-Xmx3g concat3.rb
Rehearsal -----------------------------------------------
plus 0.010000 0.000000 0.010000 ( 0.001445)
concat 0.000000 0.000000 0.000000 ( 0.002534)
splash 0.000000 0.000000 0.000000 ( 0.001791)
concat_all 0.000000 0.000000 0.000000 ( 0.002513)
flat_map 0.010000 0.000000 0.010000 ( 0.007088)
-------------------------------------- total: 0.020000sec
user system total real
plus 0.010000 0.000000 0.010000 ( 0.002700)
concat 0.000000 0.000000 0.000000 ( 0.001085)
splash 0.000000 0.000000 0.000000 ( 0.001569)
concat_all 0.000000 0.000000 0.000000 ( 0.003052)
flat_map 0.000000 0.000000 0.000000 ( 0.002252)
Rehearsal -----------------------------------------------
plus 32.410000 0.670000 33.080000 ( 17.385688)
concat 18.610000 0.060000 18.670000 ( 11.206419)
splash 57.770000 0.330000 58.100000 ( 25.366032)
concat_all 19.100000 0.030000 19.130000 ( 13.747319)
flat_map 16.160000 0.040000 16.200000 ( 10.534130)
------------------------------------ total: 145.180000sec
user system total real
plus 16.060000 0.040000 16.100000 ( 11.737483)
concat 15.950000 0.030000 15.980000 ( 10.480468)
splash 47.870000 0.130000 48.000000 ( 22.668069)
concat_all 19.150000 0.030000 19.180000 ( 13.934314)
flat_map 16.850000 0.020000 16.870000 ( 10.862716)
...所以它看起来恰恰相反 - MRI 2.3 比 JRuby 9.1
慢 2-5 倍cat concat3.rb
require 'benchmark'
N = (ENV['TIMES'] || 100).to_i
class Array
def concat_all
self.reduce([], :+)
end
end
# small arrays
a = (1..10).to_a
b = (11..20).to_a
c = (21..30).to_a
Benchmark.bmbm do |r|
r.report('plus ') { N.times { a + b + c }}
r.report('concat ') { N.times { [].concat(a).concat(b).concat(c) }}
r.report('splash ') { N.times {[*a, *b, *c]} }
r.report('concat_all ') { N.times { [a, b, c].concat_all }}
r.report('flat_map ') { N.times {[a, b, c].flat_map(&:itself)} }
end
#large arrays
a = (1..10_000_000).to_a
b = (10_000_001..20_000_000).to_a
c = (20_000_001..30_000_000).to_a
Benchmark.bmbm do |r|
r.report('plus ') { N.times { a + b + c }}
r.report('concat ') { N.times { [].concat(a).concat(b).concat(c) }}
r.report('splash ') { N.times {[*a, *b, *c]} }
r.report('concat_all ') { N.times { [a, b, c].concat_all }}
r.report('flat_map ') { N.times {[a, b, c].flat_map(&:itself)} }
end
我从这些评论和答案中学到了什么以及我后来自己做的测试..
- OS 可能有所不同,我希望得到更多答案 在不同的情况下所以我只是猜测
- 最快的方法在 运行time、MRI 或 jRuby、64 位的 32、JRE 之间有所不同,因此声称该方法比那个更好 另一个很难,在我的系统上,plus 方法是最快的 几乎所有情况,但我没有像 kares 那样使用 Java HotSpot
- 在 64 位 jRuby 中,您可以指定比 32 位高得多的堆(我的系统上为 1.5G),在 64 位中,我可以使用比我拥有的更多的堆 内存(某个地方的错误?)
- 更高的堆可以加快使用大量内存的操作,就像我使用的巨大数组一样
- 使用最新的Java运行时间,速度更好
- jRuby需要预热,一个方法在编译前需要运行多次,所以使用不同重复值的.bm和.bmbm来 找到边距
- 有时 MRI 速度更快,但使用正确的参数和预热 jRuby 在我的系统上对于这个特定的速度是 3 到 3.5 倍 测试
最后,与 JVM 的加载一起使 MRI 更适合短的即席脚本,jRuby更适合进程饥饿,较长的 运行ning 过程,方法经常重复,所以 j Ruby 更适合 运行 宁服务器和服务。
我所看到的证实了:对长时间或重复的过程进行自己的基准测试。 与早期版本相比,这两种实现在速度上都取得了很大的改进,我们不要忘记:Ruby 可能是一个较慢的 运行ner 但一个更快的开发人员,如果你比较一些额外硬件的成本与一些额外的开发人员...
感谢所有评论者和凯伦的专业知识。
编辑
出于好奇,我 运行 也在 docker 容器中使用 Rubinius 进行了测试(我在 Windows),rubinius 3.69 (2.3.1 a57071c6 2016-11-17 3.8.0) [x86_64-linux-gnu]
只有 concat 和 flat_map 与 MRI 相当,我想知道这些方法是否在 C 中,其余的在纯 Ruby..
Rehearsal -----------------------------------------------
plus 0.000000 0.000000 0.000000 ( 0.000742)
concat 0.000000 0.000000 0.000000 ( 0.000093)
splash 0.000000 0.000000 0.000000 ( 0.000619)
concat_all 0.000000 0.000000 0.000000 ( 0.001357)
flat_map 0.000000 0.000000 0.000000 ( 0.001536)
-------------------------------------- total: 0.000000sec
user system total real
plus 0.000000 0.000000 0.000000 ( 0.000589)
concat 0.000000 0.000000 0.000000 ( 0.000084)
splash 0.000000 0.000000 0.000000 ( 0.000596)
concat_all 0.000000 0.000000 0.000000 ( 0.001679)
flat_map 0.000000 0.000000 0.000000 ( 0.001568)
Rehearsal -----------------------------------------------
plus 68.770000 63.320000 132.090000 (265.589506)
concat 20.300000 2.810000 23.110000 ( 23.662007)
splash 79.310000 74.090000 153.400000 (305.013934)
concat_all 83.130000 100.580000 183.710000 (378.988638)
flat_map 20.680000 0.960000 21.640000 ( 21.769550)
------------------------------------ total: 513.950000sec
user system total real
plus 65.310000 70.300000 135.610000 (273.799215)
concat 20.050000 0.610000 20.660000 ( 21.163930)
splash 79.360000 80.000000 159.360000 (316.366122)
concat_all 84.980000 99.880000 184.860000 (383.870653)
flat_map 20.940000 1.760000 22.700000 ( 22.760643)