当我创建一个地图时,我有一段代码,如下所示:
val map = gtfLineArr(8).split(";").map(_ split """).collect { case Array(k, v) => (k, v) }.toMap
然后我使用此映射来创建我的对象:
case class MyObject(val attribute1: String, val attribute2: Map[String:String])
我正在阅读数百万行并使用迭代器转换为 MyObjects。喜欢
MyObject("1", map)
当我这样做时,它真的很慢,2'000'000 个条目超过 1 小时。
我从对象创建中删除了地图,但我仍然执行拆分过程(第 1 节(:
val map = gtfLineArr(8).split(";").map(_ split """).collect { case Array(k, v) => (k, v) }.toMap
MyObject("1", null)
对于 2'000'000 个条目,脚本在不到 1 分钟的时间内运行。
我做了一些分析,看起来像是当创建对象时,val map
到对象映射之间的分配使过程变慢。我错过了什么?
更新以更好地解释问题:
如果你看到我的代码来解释我的自我迭代超过 2000000 行,将每行转换为内部对象,迭代我这样做:
it.map(cretateNewObject).toList
这个迭代器遍历所有行,并使用函数 createNewObject
将它们转换为我的对象。
这实际上非常快,特别是使用dk14所说的大内存。 性能问题出在我的内部
`crateNewObject(val line:String)`
此函数创建一个对象
`class MyObject(val attribute1:String, val attribute2:Map[String, String])`
我的函数先行做
`val attributeArr = line.split("t")`
数组的第一个属性记录是我的对象的 attribute1,第二个属性是
`val map = attributeArr(8).split(";").map(_ split """).collect { case Array(k, v) => (k, v) }.toMap`
如果我只打印 map 中的元素数量,程序将在 2 分钟内结束,如果我将 map 传递给我的新对象行MyObject(attribute1, map)
程序真的很慢。
如果您给它们足够的内存,(0 to 2000000).toList
和(0 to 2000000).map(x => x -> x).toMap
具有相似的性能(我尝试了-Xmx4G - 4千兆字节(。 toMap
实现很多是关于克隆的,所以很多内存被"分配"/"解除分配"。因此,在内存不足的情况下,GC变得过度活跃。
当我尝试以 128Mb 运行(0 to 2000000).toList
时 - 它花了几秒钟,但(0 to 2000000).map(x => x -> x).toMap
2% 的 GC 活动 (VisualVM( 下至少花了 10 分钟,并且在内存不足的情况下死亡。
然而,当我尝试-Xmx4G
两者都很快。
附言toMap
所做的是反复将一个元素添加到前缀树中,所以它必须为每个元素克隆(Array.copy
(很多:https://github.com/scala/scala/blob/99a82be91cbb85239f70508f6695c6b21fd3558c/src/library/scala/collection/immutable/HashMap.scala#L321。
因此,toMap
重复(2000000 次(执行updated0
,这反过来又经常执行Array.copy
,这需要大量的内存分配,这(在低内存情况下(导致 GC 大部分时间都去 MarkAndSweep(慢垃圾收集((据我从 jconsole 看到(。
解决方案:无论是增加内存(-Xmx
/-Xms
JVM参数(,还是需要对数据集进行更复杂的操作,请使用Apache Spark(或任何面向批处理的map-reduce框架(之类的东西以分布式方式处理数据。