红宝石中的大数组操作非常慢

时间:2011-10-20 15:12:58

标签: ruby performance jruby

我有以下情况:

我需要在非常大的集合中找出唯一的ID列表。

例如,我有6000个ID数组(关注者列表),每个数据的大小范围在1到25000之间(他们的关注者列表)。

我希望在所有这些ID数组中获得唯一的ID列表(追随者的独特关注者)。一旦完成,我需要减去另外一个列表(另一个人跟随者列表)的ID并获得最终计数。

最后一组独特的ID增长到大约60,000,000条记录。在ruby中将数组添加到大数组时,它开始变得非常慢,只有几百万。添加到设置首先需要0.1秒,然后增加到超过4秒,达到200万(没有我需要去的地方)。

我在java中编写了一个测试程序,它可以在不到一分钟的时间内完成整个过程。

也许我在红宝石中效率低下,或者有另一种方式。由于我的主要代码是专有的,我编写了一个简单的测试程序来模拟这个问题:

big_array = []
loop_counter = 0
start_time = Time.now
# final target size of the big array
while big_array.length < 60000000
 loop_counter+=1
 # target size of one persons follower list
 random_size_of_followers = rand(5000)
 follower_list = []
 follower_counter = 0
   while follower_counter < random_size_of_followers
     follower_counter+=1
     # make ids very large so we get good spread and only some amt of dupes
     follower_id = rand(240000000) + 100000
     follower_list << follower_id
   end
 # combine the big list with this list
 big_array = big_array | follower_list
 end_time = Time.now

 # every 100 iterations check where we are and how long each loop and combine takes.
 if loop_counter % 100 == 0
   elapsed_time = end_time - start_time
   average_time = elapsed_time.to_f/loop_counter.to_f
   puts "average time for loop is #{average_time}, total size of big_array is #{big_array.length}"
   start_time = Time.now
 end
end

任何建议,是时候切换到jruby并将这样的东西移动到java?

2 个答案:

答案 0 :(得分:5)

你在那里使用的方法非常低效,所以毫不奇怪这很慢。当你试图跟踪独特的东西时,一个数组需要比Hash等价物更多的处理。

这是一个简单的重构,可以将速度提高大约100倍:

all_followers = { }
loop_counter = 0
start_time = Time.now

while (all_followers.length < 60000000)
  # target size of one persons follower list
  follower_list = []

  rand(5000).times do
    follower_id = rand(240000000) + 100000
    follower_list << follower_id
    all_followers[follower_id] = true
  end

 end_time = Time.now

 # every 100 iterations check where we are and how long each loop and combine takes.
 loop_counter += 1

  if (loop_counter % 100 == 0)
    elapsed_time = end_time - start_time
    average_time = elapsed_time.to_f/loop_counter.to_f
    puts "average time for loop is #{average_time}, total size of all_followers is #{all_followers.length}"
    start_time = Time.now
  end
end

Hash的好处在于它不可能有重复。如果您需要随时列出所有关注者,请使用all_followers.keys获取ID。

哈希占用的内存比其等效的阵列多,但这是您必须为性能付出的代价。我还怀疑这里的一个大内存消费者是生成并且似乎从未使用过的许多关注者列表,所以也许你可以完全跳过这一步。

这里的关键是Array |运算符效率不高,尤其是在非常大的数组上运行时。

答案 1 :(得分:1)

以下是使用数组,哈希和集合处理唯一对象的示例:

require 'benchmark'
require 'set'
require 'random_token'

n = 10000

Benchmark.bm(7) do |x|
  x.report("array:") do
    created_tokens = []
    while created_tokens.size < n
      token = RandomToken.gen(10)
      if created_tokens.include?(token)
        next
      else
        created_tokens << token
      end
    end
    results = created_tokens
  end

  x.report("hash:") do
    created_tokens_hash = {}
    while created_tokens_hash.size < n
      token = RandomToken.gen(10)
      created_tokens_hash[token] = true
    end
    results = created_tokens_hash.keys
  end

  x.report("set:") do
    created_tokens_set = Set.new
    while created_tokens_set.size < n
      token = RandomToken.gen(10)
      created_tokens_set << token
    end
    results = created_tokens_set.to_a
  end
end

和他们的基准:

              user     system      total        real
array:    8.860000   0.050000   8.910000 (  9.112402)
hash:     2.030000   0.010000   2.040000 (  2.062945)
set:      2.000000   0.000000   2.000000 (  2.037125)

参考文献:

ruby處理unique物件