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

问题

我正在研究涉及分片的问题。 作为问题的一部分,我需要找到以两个或更多部分划分大型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] 

更好的解决方案

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

 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代码的数量(无循环,无条件) – 运行时间超过9秒

马克的方法运行时间超过5秒

这是一个重大的胜利

带走

不要被’直觉’所迷惑 – 衡量竞争算法的表现

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

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

谢谢!

这可能不足以满足您的需求(听起来他们需要在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) 

希望这可以帮助。

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

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

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

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

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

我在这里找到了一些可能会有所帮助的东西,而且我希望只有一个小鸭子打字包装才能让它像哈希那样嘎嘎叫。

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