创建地图时,我有一段代码:

 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小时才能完成2 000 000个条目。

我从对象创建中删除了地图,但仍然进行拆分过程(第1节):
val map = gtfLineArr(8).split(";").map(_ split "\"").collect { case Array(k, v) => (k, v) }.toMap
MyObject("1", null)

脚本运行的过程不到1分钟。 2,000万个条目。

我进行了一些分析,看起来像是在创建对象时,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 GB)。 toMap实现与克隆有关,因此大量内存正在“分配” /“取消分配”。因此,如果出现内存不足的情况,GC将变得 Activity 过度。

当我尝试以128Mb运行(0 to 2000000).toList时-花费了几秒钟,但是(0 to 2000000).map(x => x -> x).toMap在10%GC Activity (VisualVM)下花费了至少2分钟,并死于内存不足。

但是,当我尝试-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框架)之类的方式以分布式方式处理数据。

09-11 18:49