Julia 朱莉娅:为什么GC速度慢,内存中有大量可变结构
与非可变结构相比,当大量可变结构加载到内存中时,为什么垃圾收集会慢得多?在这两种情况下,对象树的大小应该相同Julia 朱莉娅:为什么GC速度慢,内存中有大量可变结构,julia,Julia,与非可变结构相比,当大量可变结构加载到内存中时,为什么垃圾收集会慢得多?在这两种情况下,对象树的大小应该相同 julia> struct Foo a::Float64 b::Float64 c::Float64 end julia> mutable struct Bar a::Float64 b::Float64 c::Float64
julia> struct Foo
a::Float64
b::Float64
c::Float64
end
julia> mutable struct Bar
a::Float64
b::Float64
c::Float64
end
julia> @time dat1 = [Foo(0.0, 0.0, 0.0) for i in 1:1e9];
9.706709 seconds (371.88 k allocations: 22.371 GiB, 0.14% gc time)
julia> @time GC.gc(true)
0.104186 seconds, 100.00% gc time
julia> @time GC.gc(true)
0.124675 seconds, 100.00% gc time
julia> @time dat2 = [Bar(0.0, 0.0, 0.0) for i in 1:1e9];
71.870870 seconds (1.00 G allocations: 37.256 GiB, 73.88% gc time)
julia> @time GC.gc(true)
47.695473 seconds, 100.00% gc time
julia> @time GC.gc(true)
41.809898 seconds, 100.00% gc time
不可变结构可以直接存储在数组中。对于可变结构,这永远不会发生。在您的例子中,
Foo
对象都直接存储在dat1
中,因此在创建Arary之后,实际上只有一个(尽管非常大)的分配可以访问
在dat2
的情况下,每个Bar
对象都将有自己的内存分配给它,数组将包含对这些对象的引用。因此,使用dat2
,您最终会得到1G+1可到达的分配
您还可以使用Base.sizeof
来查看这一点:
julia> sizeof(dat1)
24000000000
julia> sizeof(dat2)
8000000000
您将看到dat1
是3倍大,因为每个数组条目直接包含3个Float64
s,而dat2
中的条目只占用每个指针的空间
附带说明:对于这类测试,最好使用基准测试工具。@btime
而不是内置的@time
。因为它消除了结果中的编译开销,并且多次运行代码以获得更具代表性的结果:
@btime dat1 = [Foo(0.0, 0.0, 0.0) for i in 1:1e6]
2.237 ms (2 allocations: 22.89 MiB)
@btime dat2 = [Bar(0.0, 0.0, 0.0) for i in 1:1e6]
6.878 ms (1000002 allocations: 38.15 MiB)
如上所述,这对于调试分配特别有用。对于
dat1
我们得到2个分配(一个用于Array
实例本身,一个用于数组存储其数据的内存块),而对于dat2
我们每个元素有一个额外的分配。知道为什么创建dat1
有300k个分配吗?这是汇编吗?@OscarSmith可能是的。除了简单的玩弄之外,您还需要使用BenchmarkTools
中的btime
,它将负责从基准测试结果中删除编译,并将多次运行代码以获得更好的估计。