为什么 Scala hashmap 很慢?

Why is Scala hashmap slow?

对此可以做些什么?

我有 运行 一些测试,似乎 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/Longs)。

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

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

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

而不是调用 applyscalaMap(i),如果你调用 scalaMap.get(i) 那么它和 javaMap.get(i)

一样快

source开始,申请代码是


def apply(key: A): B = get(key) match {
    case None => default(key)
    case Some(value) => value
  }
</pre> 

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

首先:使用 nanoTime 进行 JVM 基准测试非常 容易出错。使用微基准测试框架,例如 Thyme, Caliper or JMH

其次:您正在比较 mutable java hash map 和 immutable scala hash map。不可变集合可以非常快,但在某些情况下它们永远不会像可变数据结构一样快。

这是可变 java 哈希映射与不可变 scala 哈希映射的适当微基准测试:https://gist.github.com/rklaehn/26c277b2b5666ec4b372

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

更新:如果你真的想要一个以整数作为键的可变散列 hap,scala 集合库的正确选择是 scala.collection.mutable.LongMap。这使用 long as key,并且比通用 Map 具有更好的性能,因为它不必装箱值。从要点看结果。

更新 2:如果您的密钥从 AnyRef 扩展(例如字符串),那么高性能 mutable 映射的最佳选择是 scala.collection.mutable.AnyRefMap

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

Both immutable (d5ae93e) and mutable (#7348) versions were completely replaced. - They substantially outperform the old implementations in most scenarios. - The mutable versions now perform on par with the Java standard library's implementations.

对于不可变的 HashSetHashMap:

The reimplementations are based upon Compressed Hash-Array Mapped Prefix-trees (CHAMP).

See paper "Optimizing Hash-Array Mapped Tries for Fast and Lean Immutable JVM Collections" by Steindorfer and Vinju (OOPSLA'15) for more details and descriptions of low-level performance optimizations (a pre-print of the paper is available).