在Ruby中将大型哈希划分为N个较小哈希的最有效方法是什么?

时间:2009-10-14 10:37:46

标签: ruby algorithm hash

问题


我正在研究涉及分片的问题。作为问题的一部分,我需要找到以两个或更多部分划分大型Ruby散列(> 200,0000个条目)的最快方法。

  • 是否有任何非O(n)方法?

  • 是否有非Ruby即C / C ++实现?

请不要使用将哈希转换为数组并重建N个不同哈希值的简单方法回复示例。

我担心Ruby太慢而无法完成这项工作。


最初的方法


这是我尝试的第一个解决方案。吸引人的是:

  • 它不需要在哈希中盲目循环
  • 它不需要管理计数器来在分片中均匀分配成员。
  • 它短而整洁

好吧,它不是O(n),但它依赖于标准库中的方法,我认为这比编写自己的Ruby代码要快。


pivot = s.size / 2

slices = s.each_slice(pivot)

s1 = Hash[*slices.entries[0].flatten]

s2 = Hash[*slices.entries[1].flatten]


更好的解决方案

马克和迈克非常友好地提出方法。我不得不承认马克的方法感觉不对 - 它完全按照我不想要的方式行事 - 它对所有成员进行了循环并评估了有条件的情况 - 但是因为他花时间去做评估,我想我应该尝试类似的方法和基准测试。这是我的方法的改编版本(我的密钥不是数字,所以我不能逐字逐句采用他的方法)

def split_shard(s)
    shard1 = {}
    shard2 = {}


    t = Benchmark.measure do
        n = 0

        pivot = s.size / 2

        s.each_pair do |k,v|
            if n < pivot
                shard1[k] = v
            else
                shard2[k] = v
            end

            n += 1
        end
    end

    $b += t.real
    $e += s.size
    return shard1, shard2
end


结果


在这两种情况下,大量哈希都被分成碎片。测试数据集中所有散列的元素总数为1,680,324。

我的初始解决方案 - 必须更快,因为它使用标准库中的方法并最小化Ruby代码的数量(没有循环,没有条件) - 运行在 9s

Mark的方法仅在 5s

上运行

这是一场重大胜利


带走


不要被“直觉”所迷惑 - 衡量竞争算法的表现

不要担心Ruby作为一种语言的表现 - 我最初担心的是,如果我做了一千万次这样的操作,那么在Ruby中可能需要花费大量时间,但事实并非如此。

感谢Mark和Mike,他们都得到了我的帮助。

谢谢!

2 个答案:

答案 0 :(得分:5)

我不知道如何使用未经修改的“vanilla”哈希来实现这一点 - 我希望您需要进入内部以便将分区划分为某种大容量内存复制操作。你的C有多好?

我更倾向于首先考虑分区而不是创建Hash,特别是如果首先存在200K项Hash的唯一原因是要细分

编辑:在健身房考虑之后......

寻找现有解决方案的问题在于,其他人需要(a)经历过痛苦,(b)具备解决问题​​的技术能力,以及(c)感觉社区友好,足以将其释放到野外。哦,还有你的操作系统平台。

使用B-Tree而不是Hash怎么样?保持按键排序的数据,memcpy()可以遍历它。 B树检索是O(log N),在大多数情况下对Hash的影响不大。

我找到了一些可能有帮助的东西here,而且我希望只有一个小小的打字包装才能让它像哈希一样嘎嘎叫。

但仍然需要那些C / C ++技能。 (我无可救药地生锈了。)

答案 1 :(得分:3)

这可能不足以满足您的需求(听起来他们需要在C中进行扩展),但也许您可以使用Hash #select?

我同意Mike Woodhouse的想法。您是否可以在构建原始200k项哈希的相同位置构建分片?如果项目来自数据库,您可以根据密钥的某些方面或通过重复使用LIMIT 10000之类的内容将查询拆分为多个不相交的查询,以便一次抓取一个块。

其他

你好Chris,我刚刚比较了你使用Hash#select:

的方法

要求'基准'

s = {}
1.upto(200_000) { |i| s[i] = i}

Benchmark.bm do |x|
  x.report {
    pivot = s.size / 2
    slices = s.each_slice(pivot)
    s1 = Hash[*slices.entries[0].flatten]
    s2 = Hash[*slices.entries[1].flatten]
  }
  x.report {
    s1 = {}
    s2 = {}
    s.each_pair do |k,v|
      if k < 100_001
        s1[k] = v
      else
        s2[k] = v
      end
    end
  }
end

看起来Hash #select要快得多,即使它遍历每个子哈希的整个大哈希:

# ruby test.rb 
      user     system      total        real
  0.560000   0.010000   0.570000 (  0.571401)
  0.320000   0.000000   0.320000 (  0.323099)

希望这有帮助。