在不可变集中添加元素时,我会在循环中耗尽内存。集合中已经有很多对象,我想它正在消耗大量内存。我知道,在不可变集合中添加元素时,Scala会首先将现有集合复制到一个新集合中,然后在新集合中添加该元素并将返回此新集合。
因此,假设我的JVM内存为500mb,而该集合消耗了400mb。现在,在添加新元素之前,Scala尝试将旧集复制到新集中(我认为这将再次消耗400mb),现在它已经超出了JVM内存(总消耗内存800),因此它抛出了内存不足错误。 代码看起来像下面的
private def getNewCollection(myMuttableSet:Set[MyType]): Set[MyType] = {
myMuttableSet.flatMap(c => {
val returnedSet = doSomeCalculationsAndreturnASet // this method returns a large collection so duing the loop the collection grows exponentially
if (returnedSet.isEmpty) Set.empty[MyType]
else doSomeCalculationsAndreturnASet + MyType(constArg1,constArg2) (I have case class of MyType)
})
}
请告知我的理解是否正确。
答案 0 :(得分:0)
它不是那么简单,因为它取决于Set
中元素的大小。
创建新的Set
是一项 shallow 操作,它不会复制集合中的元素,它只是创建一个指向该变量的新包装器(通常是某种哈希表)。相同的对象。
如果您有一小组大型对象,那么复制该组对象可能不会占用太多存储空间,因为对象将在两组之间共享。集合中的对象使用了大部分内存,不需要复制这些对象即可创建新的集合。因此,您的400Mb可能会变为450Mb,并符合内存限制。
如果您有大量的小对象,那么复制该对象可能会使存储量增加一倍。大多数内存用于Set
本身,并且不能在原始集和副本之间共享。在这种情况下,您的400Mb可能很容易接近800Mb。
由于您的内存不足,并且您说有很多对象,所以听起来这是问题所在,但是我们需要查看代码以确保确定。
答案 1 :(得分:0)
现在,在添加新元素之前,Scala现在尝试在此步骤中将旧集复制到新集中(我认为它将再次消耗400mb),
这是不正确的。
scala中的不可变集合(包括Sets
)被实现为persistent data structures,它们通常具有一个称为“结构共享”的属性。这意味着,在更新结构时,它不会被完全复制,而是大部分被重用,只有相对较小的一部分实际上是从头开始重新创建的。
最简单的示例是List
,它是作为单链接列表实现的,其根指向头部。
例如,您具有以下代码:
val a = List(3,2,1)
val b = 4 :: a
val c = 5 :: b
尽管三个列表的总和为3 + 4 + 5 = 12个元素,但它们在物理上共享节点,并且只有5个List
节点。
5 → 4 → 3 → 2 → 1
↑ ↑ ↑
c b a
类似的原则适用于Set
。 scala中的Set
被实现为HashTrie。我不会详细介绍Trie的细节,而只是将其视为具有高分支因子的树。现在,当该树被更新时,它不会被完全复制。仅复制从树根到新节点/更新节点的路径中的节点。
对于HashTrie
,树的深度不能超过7级。因此,在scala中更新Set
时,您将在最坏的情况下查看与O(7 * 32)
成比例的内存分配(最大为7个级别,每个节点大约是32个数组),而不考虑Set大小。
看着您的代码,您的内存中有以下内容:
myMuttableSet
存在直到getNewCollection
返回myMuttableSet.flatMap
在下面创建可变缓冲区。同样,在完成flatMap
之后,buffer.result
将把可变缓冲区的内容复制到不可变集合中。因此,实际上有一小段时间就会存在两个集合。flatMap
的每一步中,returnedSet
还会保留内存。 旁注:如果您已经将结果缓存在doSomeCalculationsAndreturnASet
中,为什么还要再次调用returnedSet
?可能是问题的根源吗?
因此,在任何给定时间点,您都拥有内存(以较大者为准):
myMuttableSet
+ mutable result set buffer
+ returnedSet
+ (another?) result doSomeCalculationsAndreturnASet
myMuttableSet
+ mutable result set buffer
+ immutable result set
总而言之,无论您的内存问题是什么,将元素添加到Set中的可能性都不大。我的建议是在调试器中暂停程序,并使用任何探查器(例如VisualVM)在不同阶段进行堆转储。