Ruby:为什么Array.sort对于大型对象来说速度慢?

时间:2010-03-13 18:07:20

标签: ruby performance sorting

一位同事需要在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

3 个答案:

答案 0 :(得分:6)

sort绝对不会复制对象。我可以想象使用EventSorter的代码和没有它的代码(你没有提供,所以我必须猜测)之间的一个区别是EventSorter只调用event.sqnevent.time一次并存储导致变量。在排序期间,只需要访问变量。每次调用排序块时,原始版本可能会调用sqntime

如果是这种情况,可以使用sort_by而不是sort来修复它。 sort_by仅对每个对象调用一次块,然后使用块的缓存结果进行进一步比较。

答案 1 :(得分:2)

正如解释可能发生的事情以及如何处理它......

排序往往会多次查看一个元素,因此对对象或结构进行昂贵的查找会很快变得非常昂贵。

在对复杂对象或结构的数组进行排序时,通常使用Schwartzian变换。基本思想是预先计算一个准确反映大结构或对象的简单值,然后对值进行排序,然后使用生成的排序数组来引用它们来自的东西。

http://en.wikipedia.org/wiki/Schwartzian_transform

答案 2 :(得分:0)

没有什么能比实际的语言源代码更好地回答这样的问题。数组排序#!使用在array.c中定义的sort_internal():

sort_internal()

(是的,我知道这是1.8.4的来源,但我无法在线找到1.8.6,并且我很确定这没有改变。)