为什么Scala hashmap变慢?

时间:2015-02-26 14:27:58

标签: scala hashmap java-8 scala-2.11

可以做些什么呢?

我已经运行了一些测试,似乎Scala Hashmap比Java HashMap慢得多。请证明我错了!

对我而言,Hashmap的重点是快速访问给定密钥的值。因此,当速度很重要时,我发现自己会使用Java HashMap,这有点让人伤心。我没有足够的经验肯定地说,但似乎你混合Java和Scala越多,你可能面临的问题就越多。

test("that scala hashmap is slower than java") {
    val javaMap = new util.HashMap[Int,Int](){
      for (i <- 1 to 20)
      put(i,i+1)
    }

    import collection.JavaConverters._
    val scalaMap = javaMap.asScala.toMap

    // check is a scala hashmap
    assert(scalaMap.getClass.getSuperclass === classOf[scala.collection.immutable.HashMap[Int,Int]])

    def slow = {
      val start = System.nanoTime()
      for (i <- 1 to 1000) {
        for (i <- 1 to 20) {
          scalaMap(i)
        }
      }
      System.nanoTime() - start
    }

    def fast = {
      val start = System.nanoTime()
      for (i <- 1 to 1000) {
        for (i <- 1 to 20) {
          javaMap.get(i)
        }
      }
      System.nanoTime() - start
    }

    val elapses: IndexedSeq[(Long, Long)] = {
      (1 to 1000).map({_ => (slow,fast)})
    }

    var elapsedSlow = 0L
    var elapsedFast = 0L
    for ((eSlow,eFast) <- elapses) {
      elapsedSlow += eSlow
      elapsedFast += eFast
    }

    assert(elapsedSlow > elapsedFast)

    val fraction : Double = elapsedFast.toDouble/elapsedSlow
    println(s"slower by factor of: $fraction")
}

我错过了什么吗?

答案摘要

截至目前,在将Java 8与Scala 2.11进行比较时,看起来Java HashMap在查找速度方面(对于少量密钥)的速度明显快于Scala产品 - 除了LongMap(如果您的密钥是Ints /朗斯)。

性能差异不是很大,在大多数用例中应该都很重要。希望Scala能够提高地图的速度。同时,如果您需要性能(使用非整数键),请使用Java。

Int键,n = 20
Long(60),Java(93),Open(170),MutableSc(243),ImmutableSc(317)

案例对象键,n = 20
Java(195),AnyRef(230)

3 个答案:

答案 0 :(得分:30)

首先:使用nanoTime执行JVM基准测试容易出错。使用微基准测试框架,例如ThymeCaliperJMH

第二:您正在将可变 java哈希映射与不可变 scala哈希映射进行比较。不可变集合可以非常快,但在某些情况下它们永远不会像可变数据结构那样快。

以下是可变java哈希映射与不可变scala哈希映射的正确微基准测试:https://gist.github.com/rklaehn/26c277b2b5666ec4b372

如您所见,scala不可变映射比java可变映射快一点。请注意,一旦您转到较大的地图,情况就不是这样了,因为不可变数据结构必须做出一些妥协来启用structural sharing。我猜想在这两种情况下,主要的性能问题是将整数加入整数。

更新:如果你真的想要一个带有整数的可变哈希,那么scala集合库中的正确选择是scala.collection.mutable.LongMap。这使用long作为键,并且具有比通用Map更好的性能,因为它不必包装该值。查看要点的结果。

更新2:如果你的密钥来自AnyRef(例如字符串),那么高性能可变地图的最佳选择是scala.collection.mutable.AnyRefMap

答案 1 :(得分:12)

如果你apply scalaMap(i),那么它不会调用scalaMap.get(i),而是调用javaMap.get(i),而不是 def apply(key: A): B = get(key) match { case None => default(key) case Some(value) => value }

source开始,申请代码为

get

表明apply方法首先调用option方法,然后对其进行模式匹配。在{{1}}的情况下,每次调用都有一个额外的跃点确实会有性能损失,并且已经在SO上进行了讨论(虽然找不到链接)

答案 2 :(得分:1)

Scala 2.13(2019年6月)确实引入了新的,更快的HashMap/Set实现

  

不可变(d5ae93e)和可变(#7348)版本均被完全替换。   -在大多数情况下,它们的性能大大优于旧的实现。   -可变版本现在可以与Java标准库的实现相提并论。

对于不变的HashSetHashMap

  

重新实现基于压缩哈希数组映射的前缀树 CHAMP )。

     

有关低级性能优化(a pre-print of the paper is available)的更多详细信息和说明,请参见Steindorfer和Vinju(OOPSLA'15)发表的论文“为快速且精简的不可变JVM集合优化哈希数组映射尝试”。 >