3
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?仅仅因为它很优雅?

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

4

6 回答 6

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

更好的数学总是更快:)

于 2011-10-06T20:45:31.960 回答
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)
于 2011-10-06T20:34:56.680 回答
3

请尝试以下操作:

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

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

于 2011-10-06T20:22:45.740 回答
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
于 2011-10-06T20:24:43.587 回答
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)

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

于 2011-10-07T05:03:10.523 回答
1

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

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

于 2011-10-06T20:34:58.270 回答