2

以前の関連する質問に基づいて、Enumeratorクラスを使用して無限のシーケンスを生成すると、パフォーマンスに大きなギャップがあることを発見しました。私が信じる前は、問題はEnumerable方法takeにあると信じていましdropたが、次のベンチマークはこの主張を確認していません。

自然数ジェネレーターを作成し、間隔(10e7-10、10e7>:)で数値を表示する例

require 'benchmark'

nats_range = (1..Float::INFINITY)
nats_enum = Enumerator.new {|y| i=1; loop { y << i; i+=1 }}

puts "#{'_'*79+"\n"}Benchmarking Enumerable methods on Range ..."
puts Benchmark.measure { print nats_range.take(10**7).drop(10**7-10), "\n" }

puts "#{'_'*79+"\n"}Benchmarking Enumerable methods on Enumerator ..."
puts Benchmark.measure { print nats_enum.take(10**7).drop(10**7-10), "\n" }


$ ruby a.rb 
_______________________________________________________________________________
Benchmarking Enumerable methods on Range ...
[9999991, 9999992, 9999993, 9999994, 9999995, 9999996, 9999997, 9999998, 9999999, 10000000]
  1.570000   0.010000   1.580000 (  1.576761)
_______________________________________________________________________________
Benchmarking Enumerable methods on Enumerator ...
[9999991, 9999992, 9999993, 9999994, 9999995, 9999996, 9999997, 9999998, 9999999, 10000000]
 15.620000   0.020000  15.640000 ( 15.665156)

列挙子を使用した同等のコードは10倍遅くなります!

私はここで、この大きな違いを誰かが説明できるかどうか尋ねています。列挙子を不適切に使用しますか?これは現在のRuby実装での既知のリグレッションですか?

MRI Ruby 1.9.3p385

4

2 に答える 2

3

Enumerators は s に基づいておりFiber、非常に軽量なスレッドと考えることができます。(実際には、それらはコルーチンです。)

Rangesucc繰り返しと<=、まだ最後に達しているかどうかを判断するために使用します。

したがって、あなたの例では、とへのRange2,000 万回のメソッド呼び出しを使用しています。どちらも高度に最適化されており、基本的に対応するアセンブリ命令に多かれ少なかれ直接マップされています。Fixnum#succFixnum#<=

あなたのEnumerator例では、2000万回の呼び出しEnumerator::Yielder#<<(それがどれほど高価かを知っている人)と1000万回のコンテキストスイッチFixnum#+ を使用しています。コンテキストの切り替えは、単純な操作の 10 倍のコストがかかるFiberことは容易に想像できます。FiberFixnum

于 2013-02-20T22:12:49.927 に答える
0

あなたが正しい質問をしているとは思いません。

10 個の要素が必要で、100 億個を生成および保存している場合、そもそもアルゴリズムに問題がある可能性があります。

さらに、 があなたが求める答えを与えることができない場合、をと比較しても意味がありEnumeratorません。RangeRange

マイクロベンチマークを実行するのは楽しいですが、通常は無意味であることを覚えておいてください。とにかく、これは私がより合理的な制限で得たものです。

class AllNumbers
  include Enumerable
  def each
    i = 0
    loop { yield i += 1 }
  end
end

custom = AllNumbers.new
enum = Enumerator.new do |y|
  i=0
  loop { y << i+=1 }
end
range = 1..Float::INFINITY
require 'fruity'

limit = 1000
compare do
  using_range        {  range.take(limit) }
  using_enumerator   {   enum.take(limit) }
  using_custom_class { custom.take(limit) }
end

結果は私を少し驚かせます:

using_custom_class is faster than using_range by 20.0% ± 10.0%
using_range is faster than using_enumerator by 70.0% ± 10.0%

私はそれを推測しなかったでしょう。実際、Range#eachは整数に対して最適化されていませんが、 は最適化されてい+=ます。

于 2013-02-20T23:07:00.087 に答える