【问题标题】:Scala: Hash ignores initial size (fast hash table for billions of entries)Scala:哈希忽略初始大小(数十亿条目的快速哈希表)
【发布时间】:2012-10-31 20:55:41
【问题描述】:

我试图找出 Scala 的散列函数对大型散列表(具有数十亿个条目,例如存储特定 DNA 出现的频率)的扩展程度。

然而,有趣的是,HashMap 和 OpenHashMap 似乎都忽略了指定初始大小的参数(2.9.2. 和 2.10.0,最新版本)。

我认为之所以如此,是因为在前 800.000 左右之后添加新元素会变得慢得多。

我尝试增加要插入的字符串中的熵(只有下面代码中的字符 ACGT),但没有效果。

对这个具体问题有什么建议吗?我也很高兴听到您对使用 Scala 的内置类型对于具有数十亿个条目的哈希表是否是一个好主意的意见。

import scala.collection.mutable.{ HashMap, OpenHashMap }    
import scala.util.Random

object HelloWorld {
    def main(args: Array[String]) {


        val h = new collection.mutable.HashMap[String, Int] {
            override def initialSize = 8388608
        }

        // val h = new scala.collection.mutable.OpenHashMap[Int,Int](8388608); 



        for (i <- 0 until 10000000) {
            val kMer = genkMer()

            if(! h.contains(kMer))
            {
                h(kMer) = 0;
            }
            h(kMer) = h(kMer) + 1;

            if(i % 100000 == 0)
            {
                println(h.size);
            }
        }

        println("Exit. Hashmap size:\n");
        println(h.size);

    }

    def genkMer() : String =
    {
        val nucs = "A" :: "C" :: "G" :: "T" :: Nil

        var s:String = "";
        val r = new scala.util.Random
        val nums = for(i <- 1 to 55 toList) yield r.nextInt(4) 
        for (i <- 0 until 55) {
            s = s + nucs(nums(i))
        }
        s
    }
}

【问题讨论】:

  • 你不会内存不足吗?
  • 32 位还是 64 位 jvm?关于忽略初始大小:没有,你可以查看HashMap的源代码
  • 感谢您的回答。澄清一下,这将部署在具有 256G+ RAM 的机器上。 @Noah:但是每次翻倍后它都必须复制存储桶内容,对吗?但即使这是真的,它也无法向我解释为什么这种性能下降会发生在 800.000 次左右的迭代之后——我预计在进行重新排列时会急剧下降,然后再恢复到全速。
  • @Arjan:64 位。除了我描述的性能下降之外,无论我设置什么初始大小,我的程序的内存占用都不会改变。
  • 在下面查看我的更新,你需要增加你的最大 heep size。

标签: scala hash hashmap


【解决方案1】:

我不会使用 Java 数据结构来管理数十亿条目的映射。原因:

  • Java HashMap 中的最大桶数为 2^30 (~1B),因此
    • 使用默认加载因子,当地图在 7.5 亿个条目后尝试调整大小时,您将失败
    • 您需要使用 > 1 的负载系数(例如,理论上 5 可以为您带来 50 亿件商品)
    • 在高负载因子的情况下,您将遇到大量哈希冲突,并且读写性能将开始严重下降
    • 一旦您实际超过 Integer.MAX_INTEGER 值,我不知道存在什么问题 - 例如,地图上的 .size() 将无法返回实际计数
  • 我会非常担心在 Java 中运行 256 GB 堆 - 如果您遇到完整的 GC,它将锁定世界很长时间以检查旧代中的数十亿个对象

如果是我,我会寻找一种堆外解决方案:某种数据库。如果您只是存储(哈希码,计数),那么许多键值存储之一可能会起作用。最大的障碍是找到一个可以支持数十亿条记录的记录(有些最大记录为 2^32)。

如果您可以接受一些错误,概率方法可能值得研究。我不是这里的专家,但here 列出的内容听起来很相关。

【讨论】:

    【解决方案2】:

    首先,你不能覆盖 initialSize,我认为 scala 让你,因为它是 HashTable 中的私有包:

    private[collection] final def initialSize: Int = 16
    

    其次,如果你想设置初始大小,你必须给它一个你想要的初始大小的HashTable。所以如果不从 16 开始构建这个地图确实没有什么好的方法,但它确实会增长 2 的幂,所以每次调整大小应该会变得更好。

    第三,scala集合比较慢,我推荐java/guava/etc集合代替。

    最后,数十亿的条目对于大多数硬件来说有点多,你可能会耗尽内存。您很可能需要使用内存映射文件,这是一个很好的例子(虽然没有散列):

    https://github.com/peter-lawrey/Java-Chronicle

    更新 1 这是一个很好的替代 java 集合的方法:

    https://github.com/boundary/high-scale-lib

    更新 2 我运行了你的代码,它确实减慢了大约 800,000 个条目,但后来我提高了 java 堆大小,它运行良好。尝试对 jvm 使用类似的东西:

    -Xmx2G
    

    或者,如果你想使用你记忆的最后一点:

    -Xmx256G
    

    【讨论】:

    • 我不认为 high-scale-lib 在这里会有所帮助。无论如何,不​​是与地图大小有关的问题。 high-scale-lib 提供的数据结构即使在许多 CPU 同时使用它们时也能很好地执行。我认为处理大量收藏没有什么特别之处。
    • 你认为他将如何构建十亿条目哈希图?必须使用一堆 cpu 进行多线程处理,否则将花费很长时间。
    【解决方案3】:

    这些是错误的数据结构。你会很快达到内存限制(除非你有 100+gb,即使那样你仍然会很快达到限制)。

    我不知道 scala 是否存在合适的数据结构,尽管可能有人会用 Java 做过一些事情。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-12
      • 2011-08-29
      • 2016-02-10
      • 2015-07-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多