关于node.js:为什么这个CoffeeScript比这个Ruby脚本更快?

Why is this CoffeeScript faster than this Ruby script?

我正在解决一个问题,这个问题要求我找出4000000以下所有偶数的斐波那契数之和,我注意到下面的coffeeesccript比下面的ruby执行得更快。

咖啡描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sum = 0

f = (n) ->
  if n == 0 then return 0
  else if n == 1 then return 1
  else return f(n-1) + f(n-2)

console.log"Starting..."

for i in [1..4000000]
  ff = f(i)
  break if ff >= 4000000
  sum += ff if ff % 2 == 0
  console.log".." + ff

console.log"Sum:" + sum

红宝石

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sum = 0

def F(n)
  if n.eql? 0
    return 0
  elsif n.eql? 1
    return 1
  else
    return F(n-1) + F(n-2)
  end
end

puts"Starting..."

4000000.times do |num|
  the_num = F(num)
  break if the_num >= 4000000
  sum += the_num if the_num % 2 == 0
  puts".." + the_num.to_s
end

puts"Sum:" + sum.to_s

Ruby脚本花费了近6-8秒的时间来查找4000000以下的所有偶数斐波那契数,而完成nodejs执行coffeescript所需的时间大约为0.2秒。这是为什么??

1
2
3
4
$ ruby --version
  ruby 2.1.0p0 (2013-12-25 revision 44422) [x86_64-darwin12.0]
$ node --version
  v0.10.25


让我们使用Ruby进行分析,教授:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# so_21908065.rb

sum = 0

def F(n)
  # using == instead of eql? speeds up from ~ 7s to ~ 2s
  if n == 0
    return 0
  elsif n == 1
    return 1
  else
    return F(n-1) + F(n-2)
  end
end

puts"Starting..."

1.upto(4000000) do |num|
  the_num = F(num)
  break if the_num >= 4000000
  sum += the_num if the_num % 2 == 0
  puts".." + the_num.to_s
end

puts"Sum:" + sum.to_s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ ruby-prof so_21908065.rb
 %self      total      self      wait     child     calls  name
 16.61     27.102    27.102     0.000     0.000 87403761   Fixnum#==
  9.57     15.615    15.615     0.000     0.000 48315562   Fixnum#-
  4.92      8.031     8.031     0.000     0.000 24157792   Fixnum#+
  0.00      0.000     0.000     0.000     0.000       70   IO#write
  0.00    163.123     0.000     0.000   163.123        1   Integer#upto
  0.00    163.122     0.000     0.000   163.122 48315596  *Object#F
  0.00      0.000     0.000     0.000     0.000       35   IO#puts
  0.00      0.000     0.000     0.000     0.000       34   Fixnum#to_s
  0.00      0.001     0.000     0.000     0.000       35   Kernel#puts
  0.00      0.000     0.000     0.000     0.000       34   String#+
  0.00    163.123     0.000     0.000   163.123        2   Global#[No method]
  0.00      0.000     0.000     0.000     0.000       34   Fixnum#>=
  0.00      0.000     0.000     0.000     0.000        2   IO#set_encoding
  0.00      0.000     0.000     0.000     0.000       33   Fixnum#%
  0.00      0.000     0.000     0.000     0.000        1   Module#method_added

* indicates recursively called methods

因此,主要的罪魁祸首是Fixnum#==Fixnum#-Fixnum#+

正如@holgerjust所指出的,由于JIT优化,"预热的"JRuby执行可能更快。