我注意到,当将大型数据帧保存为 CSV 时,内存分配比内存中数据帧的大小(或磁盘上 CSV 文件的大小)高一个数量级,至少高出 10 倍。 为什么这是案件?有没有办法防止这种情况?即有没有一种方法可以将数据帧保存到磁盘而不使用比实际数据帧更多的内存?
在下面的示例中,我生成了一个包含一个整数列和 10m 行的数据框。它重 76MB,但写入 CSV 分配了 1.35GB。
using DataFrames, CSV
function generate_df(n::Int64)
DataFrame!(a = 1:n)
end
Run Code Online (Sandbox Code Playgroud)
julia> @time tmp = generate_df2(10000000);
0.671053 seconds (2.45 M allocations: 199.961 MiB)
julia> Base.summarysize(tmp) / 1024 / 1024
76.29454803466797
julia> @time CSV.write("~/tmp/test.csv", tmp)
3.199506 seconds (60.11 M allocations: 1.351 GiB)
Run Code Online (Sandbox Code Playgroud)
您所看到的与 无关CSV.write,而是与DataFrame类型不稳定的事实有关。这意味着它将在迭代行并访问其内容时进行分配。下面是一个例子:
julia> df = DataFrame(a=1:10000000);
julia> f(x) = x[1]
f (generic function with 1 method)
julia> @time sum(f, eachrow(df)) # after compilation
0.960045 seconds (40.07 M allocations: 613.918 MiB, 4.18% gc time)
50000005000000
Run Code Online (Sandbox Code Playgroud)
这是一个深思熟虑的设计决策,以避免对于非常宽的数据帧(这在某些应用领域的实践中很常见)出现不可接受的编译时间。现在,这是减少分配的方法:
julia> @time CSV.write("test.csv", df) # after compilation
1.976654 seconds (60.00 M allocations: 1.345 GiB, 5.64% gc time)
"test.csv"
julia> @time CSV.write("test.csv", Tables.columntable(df)) # after compilation
0.439597 seconds (36 allocations: 4.002 MiB)
"test.csv"
Run Code Online (Sandbox Code Playgroud)
(如果表很窄,这将正常工作,对于宽表,它可能会遇到编译时间问题)
这是在 Julia 中经常遇到的模式之一(甚至 Julia 本身也以这种方式工作,因为args字段 in Expris Vector{Any}):如果您不关心性能(但想要避免过多的编译延迟),通常您可以使用类型不稳定的代码,并且很容易切换到类型稳定模式,其中编译时间无关紧要而类型稳定性无关紧要。