可以做些什么呢?
我已经运行了一些测试,似乎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)
答案 0 :(得分:30)
首先:使用nanoTime执行JVM基准测试极容易出错。使用微基准测试框架,例如Thyme,Caliper或JMH
第二:您正在将可变 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标准库的实现相提并论。
对于不变的HashSet
和HashMap
:
重新实现基于压缩哈希数组映射的前缀树( CHAMP )。
有关低级性能优化(a pre-print of the paper is available)的更多详细信息和说明,请参见Steindorfer和Vinju(OOPSLA'15)发表的论文“为快速且精简的不可变JVM集合优化哈希数组映射尝试”。 >