我有一个非常大的不可变的密钥集,不适合存储在内存中,还有一个更大的引用列表,必须只扫描一次。如何在RAM中完成标记阶段?我确实有一个可能的解决方案,稍后我会写下来作为答案(不想破坏它),但也许还有其他我没有想到的解决方案。
我将试图重申这个问题,使其更“真实”:
你在Facebook工作,你的任务是找出哪些用户从未创建过带有表情符号的帖子。你所拥有的只是活动用户名列表(大约20亿),以及帖子列表(用户名/文本),你必须扫描它们,但只扫描一次。它只包含活动用户(您不需要验证他们)。
此外,您还有一台电脑,内存为2GB(1GB可获得额外积分)。因此,它必须全部在RAM中完成(无需外部排序或按排序顺序读取)。两天之内。
你能做到吗?怎样提示:您可能需要使用一个哈希表,用户名作为键,一位作为值。但是用户名列表不适合存储,所以这不起作用。对于用户ID,它可能会起作用,但你只知道名称。你可以扫描用户名列表几次(可能是40次,但不能更多)。
创建一个最小的完美哈希函数(MPHF)。在每个键1.8位左右(使用RecSplit算法),它使用了大约429 MB。(这里,1 MB是2^20字节,1 GB是2^30字节。)对于每个用户,分配一位作为标记,大约238 MB。所以内存使用量大约是667 MB。然后读取帖子,为每个用户计算哈希,如果需要,设置相关的位。再次读取用户表,计算哈希,检查是否设置了位。
生成MPHF有点棘手,不是因为速度慢(这可能需要大约30分钟的CPU时间),而是因为内存使用。对于1GB或RAM,它需要分段完成。假设我们使用32个大小大致相同的片段,如下所示:
上面的算法读取用户列表32次。如果使用更多的段(例如一百万个),并且每一步读取内存中的尽可能多的段,这个数字可以减少到大约10次。对于较小的段,每个键需要更少的比特来降低一个段内重复的概率。
对键进行散列,对散列进行排序,并以压缩形式存储排序后的散列。
TL; DR
我提出的算法可以看作是类似(更简单)问题解决方案的扩展。
虽然使用值h=2*number_的_键似乎相当不错,但我们可以尝试改变它以优化空间需求。(设置太高会降低压缩比,设置太低会导致重复太多)。
这种方法不能保证结果:有可能发明10个坏的哈希函数,这样每个键在每一次传递中都会被复制。但是很有可能它会成功,并且很可能需要大约1GB的内存(因为大多数压缩的整数值都在[1...8]的范围内,所以每个键在压缩流中会产生大约2...3位)。
为了精确估计空间需求,我们可能需要(复杂?)数学证明或算法的完整实现(也相当复杂)。但是要获得粗略的估计,我们可以使用步骤1...4的部分实现。在Ideone上看到它。它使用了名为FSE的ANS编码器的变体(摘自这里:https://github.com/Cyan4973/FiniteStateEntropy)和简单的哈希函数实现(摘自这里:https://gist.github.com/badboy/6267743)。以下是结果:
Key list loads allowed: 10 20
Optimal h/n: 2.1 1.2
Bits per key: 2.98 2.62
Compressed MB: 710.851 625.096
Uncompressed MB: 40.474 3.325
Bitmap MB: 238.419 238.419
MB used: 989.744 866.839
Index entries: 1'122'520 5'149'840
Indexed fragment size: 1781.71 388.361
由于最初的操作限制为10个键扫描,散列范围的最佳值仅略高于我的猜测(2.1),这个参数非常方便,因为它允许使用32位散列(而不是64位散列)。所需的内存略小于1GB,这允许使用相当大的索引(因此第10步不会很慢)。这里有一个小问题:这些结果显示了最终消耗了多少内存,但是在这个特定的情况下(10个键扫描),我们在执行第二次扫描时暂时需要超过1GB的内存。如果我们删除第一次通过的结果(唯一哈希)并在稍后与步骤7一起重新计算,这可能是固定的。
由于没有20个键扫描的严格限制,散列范围的最佳值为1.2,这意味着算法需要更少的内存,并为索引留出更多空间(因此步骤10将几乎快5倍)。
放宽对40键扫描的限制不会带来任何进一步的改进。
听起来像是我10年前解决的问题。
第一阶段:沟渠GC。小对象(几个字节)的GC开销可能超过100%。
第二阶段:设计一个合适的用户名压缩方案。英语每个字符大约有3位。即使允许更多字符,平均比特数也不会快速增加。
第三阶段:在内存中创建用户名词典。使用每个用户名的16位前缀来选择正确的子字典。读入所有用户名,最初仅按此前缀对它们进行排序。然后依次对每本字典进行排序。如问题中所述,为“used emoji”结果为每个用户名分配一个额外的位。
现在的问题是I/O受限的,因为计算是令人尴尬的并行。最长的阶段将是阅读所有帖子(这将是很多TB)。
请注意,在这个设置中,您没有使用像String
这样的奇特数据类型。字典是连续的内存块。
不过,如果最后期限是两天,我会放弃一些这种幻想。用于读取文本的I/O限制非常严重,用户数据库的创建可能会超过16GB。是的,可以换成磁盘。一次性的大不了。
问题内容: 我很好奇嵌套函数的node.js模式如何与v8的垃圾收集器一起工作。这是一个简单的例子 如果restofprogram是长时间运行的,那是否不意味着str将永远不会被垃圾回收?我的理解是,使用结点,您最终会获得很多嵌套函数。如果在外部声明了restofprogram,是否会收集垃圾,因此str不能在范围内?这是推荐做法吗? 编辑 我不想使问题复杂化。那只是粗心,所以我修改了它。 问题答
[GC(分配失败)[defnew:10931K->472K(12288K),0.0053905秒]10931K->10712K(39616K),0.0054285秒][times:user=0.00 sys=0.00,real=0.01秒] [GC(分配失败)[defnew:10712k->472k(12288k),0.0057686秒]20952k->20952k(39616k),0.00580
Kubernetes 垃圾收集器的角色是删除指定的对象,这些对象曾经有但以后不再拥有 Owner 了。 注意:垃圾收集是 beta 特性,在 Kubernetes 1.4 及以上版本默认启用。 Owner 和 Dependent 一些 Kubernetes 对象是其它一些的 Owner。例如,一个 ReplicaSet 是一组 Pod 的 Owner。具有 Owner 的对象被称为是 Owner
问题内容: 如果我使用String.intern()来提高性能,因为我可以使用“ ==”来比较内部字符串,是否会遇到垃圾回收问题?内联字符串的垃圾回收机制与普通字符串有何不同? 问题答案: 实际上,这不是垃圾收集优化,而是字符串池优化。调用时,您用其基本引用(首次遇到此字符串的引用,如果尚不知道此引用,则为参考)替换对初始String的引用。 但是,一旦您的字符串不再在应用程序中使用,它将成为垃圾
垃圾回收 我们对生产中花了很多时间来调整垃圾回收。垃圾回收的关注点与Java大致相似,尽管一些惯用的Scala代码比起惯用的Java代码会容易产生更多(短暂的)垃圾——函数式风格的副产品。Hotspot的分代垃圾收集通常使这不成问题,因为短暂的(short-lived)垃圾在大多情形下会被有效的释放掉。 在谈GC调优话题前,先看看这个Attila的报告,它阐述了我们在GC方面的一些经验。 Scal
对于开发者来说,JavaScript 的内存管理是自动的、无形的。我们创建的原始值、对象、函数……这一切都会占用内存。 当我们不再需要某个东西时会发生什么?JavaScript 引擎如何发现它并清理它? 可达性(Reachability) JavaScript 中主要的内存管理概念是 可达性。 简而言之,“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。 这里列出固有的可达值的