7

私はこのコードブロックを持っています:

date_counter = Time.mktime(2011,01,01,00,00,00,"+05:00")
@weeks = Array.new
(date_counter..Time.now).step(1.week) do |week|
   logger.debug "WEEK: " + week.inspect
   @weeks << week
end

技術的には、コードは機能し、次を出力します。

Sat Jan 01 00:00:00 -0500 2011
Sat Jan 08 00:00:00 -0500 2011
Sat Jan 15 00:00:00 -0500 2011
etc.

しかし、実行時間は完全にゴミです!毎週の計算には約 4 秒かかります。

このコードに欠けているグロテスクな非効率性はありますか? それは十分に簡単に思えます。

Rails 3.0.3 で Ruby 1.8.7 を実行しています。

4

2 に答える 2

6

MRI と Rubinius が同様の方法を使用して範囲を生成すると仮定すると、すべての余分なチェックといくつかの Fixnum 最適化などが削除された状態で使用される基本アルゴリズムは次のようになります。

class Range
  def each(&block)
    current = @first
    while current < @last
      yield current
      current = current.succ
    end
  end

  def step(step_size, &block)
    counter = 0
    each do |o|
      yield o if counter % step_size = 0
      counter += 1
    end
  end
end

( Rubinius のソースコードを参照)

Timeオブジェクトの場合、#succ1 秒後の時間を返します。そのため、毎週だけ要求している場合でも、とにかく 2 つの時間の間に毎秒ステップスルーする必要があります。

編集:解決策

最適化された実装があるため、Fixnum の範囲を構築しますRange#step。何かのようなもの:

date_counter = Time.mktime(2011,01,01,00,00,00,"+05:00")
@weeks = Array.new

(date_counter.to_i..Time.now.to_i).step(1.week).map do |time|
  Time.at(time)
end.each do |week|
  logger.debug "WEEK: " + week.inspect
  @weeks << week
end
于 2011-03-13T02:26:57.153 に答える
4

はい、あなたはひどい非効率性を見逃しています。irbでこれを試して、何をしているかを確認してください。

(Time.mktime(2011,01,01,00,00,00,"+05:00") .. Time.now).each { |x| puts x }

範囲演算子は1月1日から現在まで1秒単位で実行され、これは膨大なリストです。残念ながら、Rubyは、範囲の生成と1週間のチャンクを1つの操作に組み合わせるほど賢くはないため、最大600万のエントリリスト全体を作成する必要があります。

ところで、「まっすぐ進む」と「ひどい非効率」は相互に排他的ではなく、実際、それらはしばしば同時の状態です。

更新:これを行う場合:

(0 .. 6000000).step(7*24*3600) { |x| puts x }

その後、出力はほぼ瞬時に生成されます。したがって、問題は、ある範囲のTimeオブジェクトに直面したときに、Rangeがチャンクを最適化する方法を知らないことですが、Fixnum範囲を使用すると非常にうまく理解できます。

于 2011-03-13T02:07:32.953 に答える