Ruby Map / Reduce功能是否必须高效?

时间:2011-10-06 20:15:02

标签: ruby functional-programming reduce inject

b1 = Time.now
puts (1..100000).inject(0) { |x, y| x + y }
a1 = Time.now
puts "Time for inject: #{a1 - b1}"

b2 = Time.now
sum = 0
(1..100000).each do |value|
    sum += value
end
puts sum
a2 = Time.now
puts "Time for each: #{a2 - b2}"

上面的Ruby代码比较了两种求和整数的方法。令我惊讶的是,更优雅的 inject reduce 方法胜过另一方。为什么会这样?为什么人们会使用低效的注入 reduce ?只是因为它很优雅?

PS:感谢所有鼓舞人心的答案。我的目的是询问导致这些差异的幕后情况。

6 个答案:

答案 0 :(得分:6)

在这种情况下我会用一点点数学:

require "benchmark"

N = 5_000_000

Benchmark.bmbm do |bm|
  bm.report "inject 1" do
    (1..N).inject(0) { |x, y| x + y }
  end

  bm.report "inject 2" do
    (1..N).inject(:+)
  end

  bm.report "each" do
    sum = 0
    (1..N).each do |value|
      sum += value
    end
  end

  bm.report "sum of finite arithmetic progression" do
    ((1 + N) * N) / 2
  end
end

结果是:

% ruby sum.rb
Rehearsal ------------------------------------------------------------------------
inject 1                               0.500000   0.000000   0.500000 (  0.507497)
inject 2                               0.320000   0.000000   0.320000 (  0.322675)
each                                   0.370000   0.000000   0.370000 (  0.380504)
sum of finite arithmetic progression   0.000000   0.000000   0.000000 (  0.000005)
--------------------------------------------------------------- total: 1.190000sec

                                           user     system      total        real
inject 1                               0.500000   0.000000   0.500000 (  0.507697)
inject 2                               0.320000   0.000000   0.320000 (  0.322323)
each                                   0.370000   0.000000   0.370000 (  0.380307)
sum of finite arithmetic progression   0.000000   0.000000   0.000000 (  0.000004)
% 

更好的数学总是更快:)

答案 1 :(得分:5)

是的,代码可读性比微优化更重要。即使采用数百万元素的总和,差异也几乎不可察觉。此外,两种方法都是O(n),因此随着元素数量的增加,两种方法都不会明显优于另一种方法。

正如其他人所指出的,inject(:+)还要快一点。即使它不是,选择最容易看到的那个,并且不要担心性能上的细微差别。这可能不是您申请中的瓶颈。

require "benchmark"

N = 5_000_000

Benchmark.bmbm do |bm|
  bm.report "inject 1" do
    (1..N).inject(0) { |x, y| x + y }
  end

  bm.report "inject 2" do
    (1..N).inject(:+)
  end

  bm.report "each" do
    sum = 0
    (1..N).each do |value|
      sum += value
    end
  end
end

结果:

               user     system      total        real
inject 1   0.610000   0.000000   0.610000 (  0.613080)
inject 2   0.370000   0.000000   0.370000 (  0.370892)
each       0.570000   0.000000   0.570000 (  0.568266)

答案 2 :(得分:3)

请尝试以下方法:

puts (1..100000).inject(:+)

就我个人而言,如果单线注入可以替换3条线,只要它不会变得混乱,我会选择注射。

答案 3 :(得分:3)

@derp是对的。我建议你下次使用基准模块,如:

#!/usr/bin/env ruby

require "benchmark"

Benchmark.bm do |x|
  x.report { (1..10000000).inject(:+) }
  x.report { sum = 0; (1..10000000).each { |value| sum += value } }
end

答案 4 :(得分:2)

值得注意的是,大多数或所有之前的答案可能都是最新的主要版本的ruby(1.9)。在1.8.7中,这种差异更明显:

$ ruby -v
ruby 1.8.7 (2011-02-18 patchlevel 334) [i686-darwin10.6.0], MBARI 0x6770, Ruby Enterprise Edition 2011.03
$ ruby bench.rb 

Rehearsal ------------------------------------------------------------------------
inject 1                               3.910000   0.010000   3.920000 (  3.932388)
inject 2                               0.660000   0.000000   0.660000 (  0.662330)
each                                   1.120000   0.010000   1.130000 (  1.126276)
sum of finite arithmetic progression   0.000000   0.000000   0.000000 (  0.000009)
--------------------------------------------------------------- total: 5.710000sec

                                           user     system      total        real
inject 1                               3.930000   0.010000   3.940000 (  3.956084)
inject 2                               0.680000   0.000000   0.680000 (  0.685073)
each                                   1.110000   0.000000   1.110000 (  1.109675)
sum of finite arithmetic progression   0.000000   0.000000   0.000000 (  0.000009)

绝对同意可读性&维护更重要。

答案 5 :(得分:1)

Ruby主要不是关于表演。如果你想要表演,可以选择其他语言。

Ruby非常乐意编写优雅的代码:)