不变性和内存使用
Immutability and memory usage
我原以为在向不可变 Seq 添加元素后,内存使用量的增加非常小。因为新引用 (val
) 可以重新使用为已创建的 val
分配的内存,并且只需要为新元素分配额外的内存。换句话说,我认为没有必要创建防御副本(参见此处https://users.scala-lang.org/t/inquiry-on-immutabillity-of-objects-and-how-they-affect-memory/2340)。
然而,实际上,添加一个元素后,内存使用量增加到与创建全新 Seq 相同的数量。以下是 SBT 项目的代码片段
object obj extends App {
val n = 1000000
def usedMem = {
val runtime = Runtime.getRuntime
runtime.totalMemory - runtime.freeMemory
}
val m1 = usedMem
val ar = scala.collection.mutable.ArrayBuffer.fill(n)("hohohohoho")
val m2 = usedMem
println ("1 " + (m2-m1))
val sq = Seq.fill(n)("hohohohoho")
val m3 = usedMem
println ("2 " + (m3-m2))
val ar0 = ar :+ "yo"
val m4 = usedMem
println ("3 " + (m4-m3))
val sq0 = sq :+ "yo"
val m5 = usedMem
println ("4 " + (m5-m4))
}
在输出中,我们看到第 2 步和第 4 步之后的内存使用量增加了近 24MB。不过,我预计在第 4 步之后会有非常小的增长。
1 4103816
2 23948288
3 4546184
4 23948600
有人可以解释这种行为吗?或者如何看到内存被重新用于不可变对象。
此外,我很惊讶 ArrayBuffer 的内存使用量比 Seq 小得多(4MB 对 24MB)。也能理解这种差异会很棒。
那是因为您没有为作业使用正确的数据结构。
这里:val sq = Seq.fill(n)("hohohohoho")
你正在创建一个 Seq
但 Seq
是一个抽象类型,因此它必须选择一个具体的实现。截至撰写本文之日,此类默认值通常为 List
并且Lists
在附加时不能共享内存,他们必须复制所有内容(这意味着它也很慢)。
现在,您可以做的是使用经过优化的追加数据结构,例如 Vector
或 cats Chain
您也可以直接使用 List
并预先添加而不是附加,例如:
val sq = List.fill(n)("hohohohoho")
val sq0 = "yo" :: sq
这是我个人不喜欢Seq
的原因之一,它没有提供足够的信息来做任何有用的事情。
我原以为在向不可变 Seq 添加元素后,内存使用量的增加非常小。因为新引用 (val
) 可以重新使用为已创建的 val
分配的内存,并且只需要为新元素分配额外的内存。换句话说,我认为没有必要创建防御副本(参见此处https://users.scala-lang.org/t/inquiry-on-immutabillity-of-objects-and-how-they-affect-memory/2340)。
然而,实际上,添加一个元素后,内存使用量增加到与创建全新 Seq 相同的数量。以下是 SBT 项目的代码片段
object obj extends App {
val n = 1000000
def usedMem = {
val runtime = Runtime.getRuntime
runtime.totalMemory - runtime.freeMemory
}
val m1 = usedMem
val ar = scala.collection.mutable.ArrayBuffer.fill(n)("hohohohoho")
val m2 = usedMem
println ("1 " + (m2-m1))
val sq = Seq.fill(n)("hohohohoho")
val m3 = usedMem
println ("2 " + (m3-m2))
val ar0 = ar :+ "yo"
val m4 = usedMem
println ("3 " + (m4-m3))
val sq0 = sq :+ "yo"
val m5 = usedMem
println ("4 " + (m5-m4))
}
在输出中,我们看到第 2 步和第 4 步之后的内存使用量增加了近 24MB。不过,我预计在第 4 步之后会有非常小的增长。
1 4103816
2 23948288
3 4546184
4 23948600
有人可以解释这种行为吗?或者如何看到内存被重新用于不可变对象。
此外,我很惊讶 ArrayBuffer 的内存使用量比 Seq 小得多(4MB 对 24MB)。也能理解这种差异会很棒。
那是因为您没有为作业使用正确的数据结构。
这里:val sq = Seq.fill(n)("hohohohoho")
你正在创建一个 Seq
但 Seq
是一个抽象类型,因此它必须选择一个具体的实现。截至撰写本文之日,此类默认值通常为 List
并且Lists
在附加时不能共享内存,他们必须复制所有内容(这意味着它也很慢)。
现在,您可以做的是使用经过优化的追加数据结构,例如 Vector
或 cats Chain
您也可以直接使用 List
并预先添加而不是附加,例如:
val sq = List.fill(n)("hohohohoho")
val sq0 = "yo" :: sq
这是我个人不喜欢Seq
的原因之一,它没有提供足够的信息来做任何有用的事情。