【问题标题】:Ruby Map/Reduce Function Necessarily Efficient?Ruby Map/Reduce 函数一定高效吗?
【发布时间】:2011-12-02 13:21:47
【问题描述】:
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 代码比较了两种求和整数的方法。令我惊讶的是,更优雅的 injectreduce 方法优于其他方法。为什么会这样?为什么人们会费心使用低效的 injectreduce?仅仅因为它很优雅?

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

【问题讨论】:

  • 如果你在你做的每一件事上都计算纳秒,你就没有时间做任何有成效的事情。另请参阅“过早优化”(尽管不要使用精简版 - 正如完整引用正确地指出的那样,在极少数情况下这样的事情确实很重要)。
  • 谢谢。非常周到的建议!

标签: ruby functional-programming reduce inject


【解决方案1】:

在这种情况下,我会做一些数学运算:

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)
% 

更好的数学总是更快:)

【讨论】:

  • 最后一个太特别了。多么好的灵感!谢谢。
【解决方案2】:

是的,代码可读性比微优化更重要。即使考虑数百万个元素的总和,差异也几乎不明显。此外,这两种方法都是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)

【讨论】:

  • 谢谢!这很有帮助。
  • 而获胜者是... (1+N)*N/2
  • @steenslag,很好!尽管如果重新定义了Range#each,它会惨遭失败! ;)
【解决方案3】:

请尝试以下方法:

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

我个人追求优雅,如果单行注入可以替换 3 行,只要它不会变得混乱,我会使用注入。

【讨论】:

  • 谢谢。我刚试过这个。事情变得越来越有趣:这个比我的两个都好!那里发生了什么?
  • 代表+ 方法的符号正在元素之间发送和应用。 类似的事情可以用 map 来完成,['a', 'b', 'c'].map(&:upcase) 会返回 ['A', 'B', 'C']。我没有提到的另一点是,当您没有为注入传递起始值时,它使用第一个元素,所以基本上().inject(0){} 上的 0 对这种情况来说是没有必要的。
【解决方案4】:

@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

【讨论】:

  • 谢谢。不知道基准模块,这对于分析代码来说似乎很方便。
【解决方案5】:

有趣的是,大多数或所有以前的答案可能都假设了最新的 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)

完全同意可读性和维护性更重要。

【讨论】:

    【解决方案6】:

    Ruby 主要与性能无关。如果你想要表演,还有其他专门为此设计的语言。

    Ruby 是关于编写优雅代码的乐趣 :)

    【讨论】:

    • 是的。所以 Matz 发明 Ruby 只是为了好玩?
    • @TerryLiYifeng 我猜是这样 :) Ruby 非常适合 Web,因为它非常方便、非常优雅且非常灵活。快速不是他的属性之一,尽管它是快速解释语言。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-27
    • 1970-01-01
    相关资源
    最近更新 更多