为什么 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)
而不是调用 apply
即 scalaMap(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.
对于不可变的 HashSet
和 HashMap
:
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).
对此可以做些什么?
我有 运行 一些测试,似乎 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)
而不是调用 apply
即 scalaMap(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.
对于不可变的 HashSet
和 HashMap
:
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).