Метод шага диапазона в Ruby вызывает очень медленное выполнение? - PullRequest
6 голосов
/ 13 марта 2011

У меня есть этот блок кода:

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.

Но время выполнения - полная чушь! Каждую неделю для вычисления требуется приблизительно четыре секунды.

Есть ли в этом коде какая-то гротескная неэффективность? Это кажется достаточно простым.

Я использую Ruby 1.8.7 с Rails 3.0.3.

Ответы [ 2 ]

5 голосов
/ 13 марта 2011

Предполагая, что 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 объекта #succ возвращает время на одну секунду позже.Таким образом, даже если вы просите об этом только каждую неделю, он должен все равно проходить каждую секунду между двумя моментами.

Редактировать: Решение

Создать диапазон 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
4 голосов
/ 13 марта 2011

Да, вы упускаете грубую неэффективность.Попробуйте это в irb, чтобы увидеть, что вы делаете:

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

Оператор диапазона будет работать с 1 января по настоящее время с шагом одна секунда , и это огромный список.К сожалению, Ruby не достаточно умен, чтобы объединить генерацию диапазона и однонедельное разбиение на части в одну операцию, поэтому он должен построить весь список ~ 6 миллионов записей.

Кстати, "прямо вперед" и "брутто"неэффективность "не являются взаимоисключающими, на самом деле они часто являются параллельными условиями.

ОБНОВЛЕНИЕ : Если вы сделаете это:

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

Тогда вывод будет произведен почтимгновенно.Таким образом, похоже, что проблема в том, что Range не знает, как оптимизировать порцию, когда сталкивается с диапазоном объектов Time, но он может довольно хорошо разобраться с диапазонами Fixnum.

...