我有这段代码:
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。
答案 0 :(得分:5)
假设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
对于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
答案 1 :(得分: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范围。