Ruby的范围步骤方法导致执行速度非常慢?

我有这段代码:

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。

假设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 

是的,你错过了严重的低效率。 在irb中尝试这个,看看你在做什么:

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

范围运算符从1月1日到现在以1秒为增量,这是一个巨大的列表。 不幸的是,Ruby不够聪明,无法将范围生成和一周的分块合并为一个操作,因此它必须构建整个约600万条目列表。

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

更新 :如果你这样做:

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

然后几乎瞬间产生输出。 因此,看起来问题是Range在面对一系列Time对象时不知道如何优化分块,但它可以很好地解决Fixnum范围。