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.

但是执行时间完全是垃圾!每周计算大约需要四秒钟。

我在这段代码中是否遗漏了一些奇怪的低效率?这似乎很简单。

我正在使用 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对象#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
于 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 日到现在以一秒为增量,这是一个巨大的列表。不幸的是,Ruby 不够聪明,无法将范围生成和一周分块合并到一个操作中,因此它必须构建整个约 600 万个条目列表。

顺便说一句,“直截了当”和“严重效率低下”并不是相互排斥的,实际上它们通常是并发条件。

更新:如果你这样做:

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

然后几乎立即产生输出。因此,问题似乎在于 Range 在面对一系列 Time 对象时不知道如何优化分块,但它可以使用 Fixnum 范围很好地解决问题。

于 2011-03-13T02:07:32.953 回答