我在irb中使用Ruby的Benchmark类,我注意到Ruby在迭代时速度明显变慢。
我在没有使用Benchmark或Profiler__类的情况下做了一个简单的测试(我想也许它正在减慢速度)。
def average_test
total_time = 0
time = 0
TESTS.times do |count|
time = test
total_time = total_time + time
yield count, time
end
average = total_time / TESTS
yield 'average', average
end
def test
x = 0
start_time = Time.now
for i in 1..ITERATIONS
x = x + 1
end
end_time = Time.now
time = end_time - start_time
end
ITERATIONS = 10_000_000
TESTS = 20
# create results file
results = File.new('results.txt', 'w')
# start test
average_test {|count, time| results.print "Test #{count}: #{time}"}
results.close
以下是在irb中运行后的结果。 (在几秒钟内,抱歉)
测试0:2.390647,测试1:2.343761,测试2:2.312554,测试3:2.566792,测试4:2.665193,测试5:2.537908,测试6:2.643086,测试7:2.534492,测试8:2.589034,测试9 :2.390633,测试10:2.539533,测试11:2.385508,测试12:2.49659,测试13:2.498958,测试14:2.527309,测试15:2.462983,测试16:2.504546,测试17:2.570159,测试18:2.371447,测试19 :2.330072,
测试平均值:2.48306025(s),2483(ms)
我也在JavaScript中进行了相同的测试,只是为了比较速度。
function test() {
var start = Date.now();
var x = 0;
for (var i = 0; i < ITERATIONS; i++) {
x = x + 1;
}
var end = Date.now();
var dt = end - start;
return dt;
}
function averageTest() {
var total = 0;
for (var i = 0; i < TESTS; i++) {
var time = test();
total = total + time;
console.log('Test ' + i + ': ', time);
}
var avg = total / TESTS;
console.log('Average: ', avg);
return avg;
}
var ITERATIONS = 10000000;
var TESTS = 20;
// start test
var avgTime = averageTest(); // results
以下是Chrome中运行的JavaScript代码的结果。 (以毫秒为单位)
测试0:41,测试1:44,测试2:41,测试3:48,测试4:46,测试5:48,测试6:49,测试7:47,测试8:46,测试9 :50,测试10:41,测试11:41,测试12:47, 测试13:54,测试14:55,测试15:57,测试16:35,测试17:50,测试18:47, 测试19:49,
平均值:46.8(ms),0.0468(s)
Ruby的平均值为2483 ms,而JavaScript的平均值为46.8 ms。
为什么会有这么大的差异?是因为Ruby的运算符是方法调用,方法调用是慢还是什么?
我觉得我做错了什么。感谢。
答案 0 :(得分:4)
我尝试了几个不同的Ruby实现的基准测试,我得到了截然不同的结果。这似乎证实了我怀疑你的基准测量不是衡量你的想法。正如我在上面的评论中提到的:在编写基准时,你应该总是读取生成的本机代码,以验证实际上测量你认为它的作用。
例如,YARV基准测试套件中有一个基准测试用于测量消息调度性能,但是,在Rubinius上,消息调度完全被优化掉了,所以实际执行的唯一事情就是递增计数器变量用于基准循环。从本质上讲,它告诉你CPU的频率,仅此而已。
ruby 2.3.0dev(2015-08-08 trunk 51510)[x86_64-darwin14]
这是YARV的现有快餐店:
Test 0: 0.720945 Test 1: 0.733733 Test 2: 0.722778 Test 3: 0.734074 Test 4: 0.774355 Test 5: 0.773379 Test 6: 0.751547 Test 7: 0.708566 Test 8: 0.724959 Test 9: 0.730899 Test 10: 0.725978 Test 11: 0.712902 Test 12: 0.747069 Test 13: 0.737792 Test 14: 0.736885 Test 15: 0.751422 Test 16: 0.718943 Test 17: 0.760094 Test 18: 0.746343 Test 19: 0.764731 Average: 0.738870
正如您所看到的,运行中的性能非常一致,并且似乎与评论中发布的其他结果一致。
rubinius 2.5.8(2.1.0 bef51ae3 2015-08-09 3.5.1 JI)[x86_64-darwin14.4.0]
这是Rubinius的当前版本:
Test 0: 1.159465 Test 1: 1.063721 Test 2: 0.516513 Test 3: 0.515016 Test 4: 0.553987 Test 5: 0.544286 Test 6: 0.567737 Test 7: 0.563350 Test 8: 0.517581 Test 9: 0.501865 Test 10: 0.503399 Test 11: 0.512046 Test 12: 0.487296 Test 13: 0.533193 Test 14: 0.533217 Test 15: 0.511648 Test 16: 0.535847 Test 17: 0.490049 Test 18: 0.539681 Test 19: 0.551324 Average: 0.585061
正如您所看到的,编译器会在第二次运行期间启动,之后它会快两倍,明显快于YARV,而在前两次运行中,它比YARV慢得多。
jruby 9.0.0.0-SNAPSHOT(2.2.2)2015-07-23 89c1348 Java HotSpot(TM)64位服务器VM 25.5-b02 on 1.8.0_05-b13 + jit [darwin-x86_64] < /强>
这是JRuby的一个当前快照,它运行在一个稍微旧的版本(几个月)的HotSpot上:
Test 0: 1.169000 Test 1: 0.805000 Test 2: 0.772000 Test 3: 0.755000 Test 4: 0.777000 Test 5: 0.749000 Test 6: 0.751000 Test 7: 0.694000 Test 8: 0.696000 Test 9: 0.708000 Test 10: 0.691000 Test 11: 0.745000 Test 12: 0.752000 Test 13: 0.755000 Test 14: 0.707000 Test 15: 0.744000 Test 16: 0.674000 Test 17: 0.710000 Test 18: 0.733000 Test 19: 0.706000 Average: 0.754650
同样,编译器似乎在运行1和2之间的某个位置启动,之后它与YARV表现相当。
jruby 9.0.1.0-SNAPSHOT(2.2.2)2015-08-09 2939c73 OpenJDK 64位服务器VM 25.40-b25-internal-graal-0.7 on 1.8.0-internal-b128 + jit [darwin -x86_64] 强>
这是在未来版本的HotSpot上运行的JRuby的一个稍微更新的快照:
Test 0: 0.815000 Test 1: 0.693000 Test 2: 0.634000 Test 3: 0.615000 Test 4: 0.599000 Test 5: 0.616000 Test 6: 0.623000 Test 7: 0.611000 Test 8: 0.604000 Test 9: 0.598000 Test 10: 0.628000 Test 11: 0.627000 Test 12: 0.601000 Test 13: 0.646000 Test 14: 0.675000 Test 15: 0.611000 Test 16: 0.684000 Test 17: 0.689000 Test 18: 0.626000 Test 19: 0.639000 Average: 0.641700
同样,我们看到它在前两次运行中的模式变得更快,之后它在比YARV和其他JRuby稍微快一点的地方之间稳定,并且比Rubinius略慢。
jruby 9.0.1.0-SNAPSHOT(2.2.2)2015-08-09 2939c73 OpenJDK 64位服务器VM 25.40-b25-internal-graal-0.7 on 1.8.0-internal-b128 + jit [darwin -x86_64] 强>
这是我的最爱:JRuby + Truffle启用了Truffle并在支持Graal的JVM上运行:
Test 0: 6.226000 Test 1: 5.696000 Test 2: 1.836000 Test 3: 0.057000 Test 4: 0.111000 Test 5: 0.103000 Test 6: 0.082000 Test 7: 0.146000 Test 8: 0.089000 Test 9: 0.077000 Test 10: 0.076000 Test 11: 0.082000 Test 12: 0.072000 Test 13: 0.104000 Test 14: 0.124000 Test 15: 0.084000 Test 16: 0.080000 Test 17: 0.118000 Test 18: 0.087000 Test 19: 0.070000 Average: 0.766000
Truffle似乎需要显着量的加速时间,前三次运行非常慢,但随后它显着加快速度,留下其他所有内容在灰尘中的因子为5-10。
注意:这不是100%公平,因为JRuby + Truffle还不支持完整的Ruby语言。
另请注意:这表明,仅仅取平均值就非常误导,因为JRuby + Truffle与YARV和JRuby的平均值相同,但实际上稳态性能提高了7倍。最慢的运行(JRuby + Truffle的第1次运行)和最快的运行(JRuby + Truffle的第20次运行)之间的差异是100x。
注意#3:注意JRuby数字如何以000
结束?这是因为JRuby无法通过JVM轻松访问底层操作系统的微秒计时器,因此必须满足毫秒。在这个特定的基准测试中,它并不重要太多,但是对于更快的基准测试,它可能会显着地扭曲结果。这只是设计基准测试时必须考虑的另一件事。
为什么会有这么大的差异?是因为Ruby的运算符是方法调用,方法调用很慢还是什么?
我不这么认为。在YARV上,Fixnum#+
甚至不是方法调用,它针对静态内置运算符进行了优化。它实质上在CPU中执行寄存器内原始整数添加操作。尽可能快。
当您修补Fixnum
时,YARV只会将其视为方法调用。
Rubinius可能会优化方法调用,虽然我没有检查。
我觉得我做错了什么。
可能您的基准测量不能衡量您的想法。特别是,我相信在使用复杂优化编译器的实现中,迭代基准测试的迭代部分可能会被优化掉。
实际上,我注意到你的JavaScript和Ruby基准测试之间存在显着差异:在JavaScript中,你使用原始的for
循环,在Ruby中,你正在使用Range#each
(for … in
被翻译为each
)。如果我将Ruby和JavaScript基准测试切换到相同的while
循环,我会得到Ruby版本:YARV为223ms,Rubinius为56ms,JRuby为28ms,JRuby + Truffle为33ms。对于JS版本:Squirrelfish Extreme / Nitro(Safari)为30ms,V8 / Crankshaft(Chrome)为16ms。
或者说,换句话说:如果你测量同样的东西,它们会同样快速地结束;-)(好吧,除了YARV,然而众所周知它仍然很慢。)
所以,事实证明,Ruby和JavaScript之间的区别在于,你在中没有迭代任何东西,你只是递增一个数字,而在Ruby中,你 实际迭代数据结构(即Range
)。从Ruby中删除迭代,它与JavaScript一样快。
我创建了两个基准脚本,现在希望粗略地衡量同样的事情:
#!/usr/bin/env ruby
ITERATIONS = 10_000_000
TESTS = 20
WARMUP = 3
TOTALRUNS = TESTS + WARMUP
RESULTS = []
run = -1
while (run += 1) < TOTALRUNS
i = -1
starttime = Time.now
while (i += 1) < ITERATIONS do end
endtime = Time.now
RESULTS[run] = (endtime - starttime) * 1000
end
puts RESULTS.drop(WARMUP).reduce(:+) / TESTS
"use strict";
const ITERATIONS = 10000000;
const TESTS = 20;
const WARMUP = 3;
const TOTALRUNS = TESTS + WARMUP;
const RESULTS = [];
let run = -1;
while (++run < TOTALRUNS) {
let i = -1;
const STARTTIME = Date.now();
while (++i < ITERATIONS);
const ENDTIME = Date.now();
RESULTS[run] = ENDTIME - STARTTIME;
}
alert(RESULTS.slice(WARMUP).reduce((acc, el) => acc + el) / TESTS);
&#13;
您会注意到我增加了迭代次数,我将测试运行次数增加了一倍,并且我引入了许多预算运行,这些运行未包含在计算结果中。我还尝试使两个片段尽可能相似。 (注意:您可能必须删除一些ES6主机才能在浏览器上运行。例如,我的Safari版本不喜欢胖箭头函数文字。)
结果是:
我想真正告诉我们的是基准没有意义:-D