一位同事需要在Rails应用程序中对一组ActiveRecord对象进行排序。他尝试了显而易见的Array.sort!
,但它看起来非常缓慢,为3700个物体的阵列花了32秒。因此,为了防止这些大胖对象减慢速度,他通过排序一个小对象数组重新实现排序,然后重新排序原始的ActiveRecord对象数组以匹配 - 如下面的代码所示。田田!排序现在需要700毫秒。
这真让我感到惊讶。 Ruby的排序方法最终会复制关于该位置的对象而不仅仅是引用吗?他正在使用Ruby 1.8.6 / 7。
def self.sort_events(events)
event_sorters = Array.new(events.length) {|i| EventSorter.new(i, events[i])}
event_sorters.sort!
event_sorters.collect {|es| events[es.index]}
end
private
# Class used by sort_events
class EventSorter
attr_reader :sqn
attr_reader :time
attr_reader :index
def initialize(index, event)
@index = index
@sqn = event.sqn
@time = event.time
end
def <=>(b)
@time != b.time ? @time <=> b.time : @sqn <=> b.sqn
end
end
答案 0 :(得分:6)
sort
绝对不会复制对象。我可以想象使用EventSorter的代码和没有它的代码(你没有提供,所以我必须猜测)之间的一个区别是EventSorter只调用event.sqn
和event.time
一次并存储导致变量。在排序期间,只需要访问变量。每次调用排序块时,原始版本可能会调用sqn
和time
。
如果是这种情况,可以使用sort_by而不是sort来修复它。 sort_by仅对每个对象调用一次块,然后使用块的缓存结果进行进一步比较。
答案 1 :(得分:2)
正如解释可能发生的事情以及如何处理它......
排序往往会多次查看一个元素,因此对对象或结构进行昂贵的查找会很快变得非常昂贵。
在对复杂对象或结构的数组进行排序时,通常使用Schwartzian变换。基本思想是预先计算一个准确反映大结构或对象的简单值,然后对值进行排序,然后使用生成的排序数组来引用它们来自的东西。
答案 2 :(得分:0)
没有什么能比实际的语言源代码更好地回答这样的问题。数组排序#!使用在array.c中定义的sort_internal():
(是的,我知道这是1.8.4的来源,但我无法在线找到1.8.6,并且我很确定这没有改变。)