我想比较 Scala 中 immutable.Map 和 mutable.Map 的性能特征以进行类似的操作(即将多个映射合并为一个映射。请参阅此问题)。对于可变映射和不可变映射,我似乎都有类似的实现(见下文)。
作为测试,我生成了一个包含 1,000,000 个单项 Map[Int, Int] 的列表,并将此列表传递给我正在测试的函数。有了足够的内存,结果并不令人惊讶:mutable.Map 约为 1200 毫秒,immutable.Map 约为 1800 毫秒,使用 mutable.Map 的命令式实现约为 750 毫秒——不确定是什么导致了巨大的差异,但请随意对此也发表评论。
让我有点吃惊的可能是因为我有点厚,在 IntelliJ 8.1 中的默认运行配置下,两个可变实现都遇到了 OutOfMemoryError,但不可变集合没有。不可变测试确实运行到完成,但运行速度非常慢——大约需要 28 秒。当我增加最大 JVM 内存(大约 200MB,不确定阈值在哪里)时,我得到了上面的结果。
无论如何,这是我真正想知道的:
为什么可变实现会耗尽内存,而不可变实现不会? 我怀疑不可变版本允许垃圾收集器在可变实现之前运行并释放内存——所有这些垃圾收集都解释了不可变低内存运行的缓慢——但我想要更详细的解释比起那个来说。
下面的实现。(注意:我并不是说这些是最好的实现。请随意提出改进建议。)
def mergeMaps[A,B](func: (B,B) => B)(listOfMaps: List[Map[A,B]]): Map[A,B] =
(Map[A,B]() /: (for (m <- listOfMaps; kv <-m) yield kv)) { (acc, kv) =>
acc + (if (acc.contains(kv._1)) kv._1 -> func(acc(kv._1), kv._2) else kv)
}
def mergeMutableMaps[A,B](func: (B,B) => B)(listOfMaps: List[mutable.Map[A,B]]): mutable.Map[A,B] =
(mutable.Map[A,B]() /: (for (m <- listOfMaps; kv <- m) yield kv)) { (acc, kv) =>
acc + (if (acc.contains(kv._1)) kv._1 -> func(acc(kv._1), kv._2) else kv)
}
def mergeMutableImperative[A,B](func: (B,B) => B)(listOfMaps: List[mutable.Map[A,B]]): mutable.Map[A,B] = {
val toReturn = mutable.Map[A,B]()
for (m <- listOfMaps; kv <- m) {
if (toReturn contains kv._1) {
toReturn(kv._1) = func(toReturn(kv._1), kv._2)
} else {
toReturn(kv._1) = kv._2
}
}
toReturn
}