我正在研究涉及分片的问题。作为问题的一部分,我需要找到以两个或更多部分划分大型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,他们都得到了我的帮助。
谢谢!
答案 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)
希望这有帮助。