Ruby Map/Reduce 函数一定高效吗?

发布于 2024-12-08 13:41:46 字数 455 浏览 1 评论 0原文

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:感谢所有鼓舞人心的答案。我的目的是询问导致差异的幕后发生的事情。

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}"

The above Ruby code compares two ways of summing up integers. To my surprise, the more elegant inject or reduce approach is outperformed by the other. Why is that the case? Why do people bother using the inefficient inject or reduce? Simply because it's elegant?

PS: Thanks for all the inspiring answers. My intention was to ask what's going on behind the scene that results in the differences.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

腻橙味 2024-12-15 13:41:46

在这种情况下,我会进行一些数学计算:

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

更好的数学总是更快:)

I would go with a little math in this case:

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

And the result is:

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

A better math is always faster :)

一曲爱恨情仇 2024-12-15 13:41:46

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

Yes, code readability is more important than micro-optimisations. The difference is barely noticeable, even when taking the sum of millions of elements. Also, both methods are O(n), so neither will significantly outperform the other as the number of elements increases.

As others have pointed out, inject(:+) is a little faster still. Even if it weren't, pick the one that is easiest on the eye, and don't worry about tiny differences in performance. This will probably not be the bottleneck in your application.

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

Results:

               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)
丑丑阿 2024-12-15 13:41:46

请尝试以下操作:就

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

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

Try the following instead:

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

Personally I go for elegance, if a single line inject can replace a 3 lines each as long as It doesnt' get messy I'd go with inject.

难理解 2024-12-15 13:41:46

@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

@derp is right. I recommend you to use the benchmark module the next time like:

#!/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
压抑⊿情绪 2024-12-15 13:41:46

有趣的是,之前的大部分或全部答案可能都假设了 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)

在可读性和可读性方面完全一致。不过维护更重要。

It's interesting to note that most or all of the previous answers probably assumed the latest major version of ruby (1.9). In 1.8.7 this difference is more pronounced:

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

Absolutely agree on readability & maintenance being more important though.

软的没边 2024-12-15 13:41:46

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

Ruby 就是为了享受编写优雅代码的乐趣:)

Ruby is mostly not about performances. If you want performances there are other languages designed for that.

Ruby is all about the pleasure to write elegant code :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文