【问题标题】:What is the fastest method(s) for reading and writing a matrix of Float64 to file in julia在Julia中读取和写入Float64矩阵到文件的最快方法是什么
【发布时间】:2025-12-29 18:00:06
【问题描述】:

x = randn(100, 2)。我想将x 写入它自己的文件。该文件将包含x,并且只有x,并且x 将永远是Matrix{Float64} 类型。过去,我一直为此使用HDF5,但我觉得这太过分了,因为在这个设置中,每个文件只有一个数组。请注意JLD 使用HDF5,因此也是过度杀戮。

1) 假设我只想读取整个矩阵,那么读取和写入x 的最快方法是什么?

2)假设我可能想读取矩阵的一部分,读取和写入x 的最快方法是什么?

3) 什么是读写x 的最快方法,假设我可能想要读取矩阵的一部分,或者覆盖矩阵的一部分(但是 改变矩阵大小)?

【问题讨论】:

  • 如果x 不是太大,内存映射听起来很高效并且管理起来非常简单(使用Mmap.mmap

标签: julia


【解决方案1】:

根据上面 Tasos 提出的建议,我使用 4 种不同的方法对写入和读取进行了初步的速度测试:

  1. h5(使用 HDF5 包)
  2. jld(使用 JLD2 包)
  3. slz(使用serializedeserialize
  4. dat(写入二进制文件,使用前128位存储矩阵的维度)

我已将测试代码粘贴在此答案的底部。结果是:

julia> @time f_write_test(N, "h5")
  0.191555 seconds (2.11 k allocations: 76.380 MiB, 26.39% gc time)

julia> @time f_write_test(N, "jld")
  0.774857 seconds (8.33 k allocations: 77.058 MiB, 0.32% gc time)

julia> @time f_write_test(N, "slz")
  0.108687 seconds (2.61 k allocations: 76.495 MiB, 1.91% gc time)

julia> @time f_write_test(N, "dat")
  0.087488 seconds (1.61 k allocations: 76.379 MiB, 1.08% gc time)

julia> @time f_read_test(N, "h5")
  0.051646 seconds (5.81 k allocations: 76.515 MiB, 14.80% gc time)

julia> @time f_read_test(N, "jld")
  0.071249 seconds (10.04 k allocations: 77.136 MiB, 57.60% gc time)

julia> @time f_read_test(N, "slz")
  0.038967 seconds (3.11 k allocations: 76.527 MiB, 22.17% gc time)

julia> @time f_read_test(N, "dat")
  0.068544 seconds (1.81 k allocations: 76.405 MiB, 59.21% gc time)

因此,对于写入,写入二进制选项的性能甚至优于 serialize,速度是 HDF5 的两倍,几乎比 JLD2 快一个数量级。

对于读取,deserialize 的性能最好,而HDF5JLD2 和从二进制读取的性能都相当接近,HDF5 略领先。

我没有包含写入切片的测试,但将来可能会回到这个。显然,使用serialize 写入切片是不可能的(更不用说serialize 也面临的版本控制/系统映像问题),而且我不确定如何使用JLD2 来做到这一点。如果切片在磁盘上是连续的,我的直觉将切片写入二进制很容易击败HDF5,但如果它是非连续的,则可能会比HDF5慢得多如果@987654336 @ 方法最佳地利用了分块。如果HDF5 没有利用分块(这意味着在写入时知道你需要什么切片),那么我怀疑二进制方法会出现。

总而言之,我将采用二元法,因为我认为现阶段它显然是总体赢家。

我怀疑最终,JLD2 可能会成为首选方法,但这里有一个公平的方法(包本身非常新,因此社区没有太多时间进行优化等工作)。

测试代码如下:

using JLD2, HDF5
f_write_h5(fp::String, x::Matrix{Float64}) = h5write(fp, "G/D", x)
f_write_jld(fp::String, x::Matrix{Float64}) = @save fp x
f_write_slz(fp::String, x::Matrix{Float64}) = open(fid->serialize(fid, x), fp, "w")
f_write_dat_inner(fid1::IOStream, x::Matrix{Float64}) = begin ; write(fid1, size(x,1)) ; write(fid1, size(x,2)) ; write(fid1, x) ; end
f_write_dat(fp::String, x::Matrix{Float64}) = open(fid1->f_write_dat_inner(fid1, x), fp, "w")
f_read_h5(fp::String) = h5read(fp, "G/D")
f_read_jld(fp::String) = @load fp x
f_read_slz(fp::String) = open(deserialize, fp, "r")
f_read_dat_inner(fid1::IOStream) = begin ; d1 = read(fid1, Int) ; d2 = read(fid1, Int) ; read(fid1, Float64, (d1, d2)) ; end
f_read_dat(fp::String) = open(f_read_dat_inner, fp, "r")
function f_write_test(N::Int, filetype::String)
    dp = "/home/colin/Temp/"
    filetype == "h5" && [ f_write_h5("$(dp)$(n).$(filetype)", randn(1000, 100)) for n = 1:N ]
    filetype == "jld" && [ f_write_jld("$(dp)$(n).$(filetype)", randn(1000, 100)) for n = 1:N ]
    filetype == "slz" && [ f_write_slz("$(dp)$(n).$(filetype)", randn(1000, 100)) for n = 1:N ]
    filetype == "dat" && [ f_write_dat("$(dp)$(n).$(filetype)", randn(1000, 100)) for n = 1:N ]
    #[ rm("$(dp)$(n).$(filetype)") for n = 1:N ]
    nothing
end
function f_read_test(N::Int, filetype::String)
    dp = "/home/colin/Temp/"
    filetype == "h5" && [ f_read_h5("$(dp)$(n).$(filetype)") for n = 1:N ]
    filetype == "jld" && [ f_read_jld("$(dp)$(n).$(filetype)") for n = 1:N ]
    filetype == "slz" && [ f_read_slz("$(dp)$(n).$(filetype)") for n = 1:N ]
    filetype == "dat" && [ f_read_dat("$(dp)$(n).$(filetype)") for n = 1:N ]
    [ rm("$(dp)$(n).$(filetype)") for n = 1:N ]
    nothing
end
f_write_test(1, "h5")
f_write_test(1, "jld")
f_write_test(1, "slz")
f_write_test(1, "dat")
f_read_test(1, "h5")
f_read_test(1, "jld")
f_read_test(1, "slz")
f_read_test(1, "dat")

N = 100
@time f_write_test(N, "h5")
@time f_write_test(N, "jld")
@time f_write_test(N, "slz")
@time f_write_test(N, "dat")
@time f_read_test(N, "h5")
@time f_read_test(N, "jld")
@time f_read_test(N, "slz")
@time f_read_test(N, "dat")

【讨论】:

    【解决方案2】:

    您可以使用serialize 函数,前提是您注意文档中关于版本之间无保证等的警告。

    序列化(流::IO,值)

    以不透明格式将任意值写入流,以便可以通过反序列化读取。回读值将尽可能与原始值相同。一般来说,如果读写是不同的,这个过程是行不通的 Julia 的版本,或具有不同系统映像的 Julia 实例。 Ptr 值被序列化为全零位模式 (NULL)。

    首先将一个 8 字节的标识标头写入流。为避免写入标头,请构造一个 SerializationState 并将其用作序列化的第一个参数。另请参阅 Serializer.writeheader。

    确实,JLD(或者事实上,它的继任者,JLD2)通常是推荐的方式*。


    *您可能特别感兴趣的是以下语句:“JLD2 以包含 HDF5 子集的格式保存和加载 Julia 数据结构,而不依赖于 HDF5 C 库” 并且 “它通常优于以前的 JLD 包(有时高出多个数量级),并且通常优于 Julia 的内置序列化程序”

    【讨论】:

    • 是的,我知道serialize,但不幸的是,版本和系统映像限制对于我心目中的应用程序来说太多了。但我不知道JLD2 将不依赖于HDF5 C 库。这很酷 - 我会研究一下。
    • 嗯。只需对JLD2 进行简单的写入测试,它就会比serialize 慢一个数量级,比writedlm 慢两倍。我在话语here上问过一个问题
    • 糟糕,我的测试出错了。纠正错误后,还是比HDF5慢了4倍。。。唉……
    • 请注意,所有这些库的开销都很小,对于您的特定测试和用例来说,这些开销往往会累积。如果您的矩阵始终是浮动的,那么您可能只创建自己的二进制读/写函数,该函数根据文件指针位置访问元素(特别是如果矩阵的大小或至少列数也始终相同) .
    • 对 4 种不同的方法进行了一些基本测试,并将结果写在下面的答案中。简而言之,此时编写二进制文件是首选方法。再次感谢。
    【解决方案3】:

    Julia 有两个内置函数 readdlmwritedlm 用于执行此操作:

    julia> x = randn(5, 5)
    5×5 Array{Float64,2}:
     -1.2837    -0.641382  0.611415   0.965762   -0.962764 
      0.106015  -0.344429  1.40278    0.862094    0.324521 
     -0.603751   0.515505  0.381738  -0.167933   -0.171438 
     -1.79919   -0.224585  1.05507   -0.753046    0.0545622
     -0.110378  -1.16155   0.774612  -0.0796534  -0.503871 
    
    julia> writedlm("txtmat.txt", x, use_mmap=true)
    
    julia> readdlm("txtmat.txt", use_mmap=true)
    5×5 Array{Float64,2}:
     -1.2837    -0.641382  0.611415   0.965762   -0.962764 
      0.106015  -0.344429  1.40278    0.862094    0.324521 
     -0.603751   0.515505  0.381738  -0.167933   -0.171438 
     -1.79919   -0.224585  1.05507   -0.753046    0.0545622
     -0.110378  -1.16155   0.774612  -0.0796534  -0.503871 
    

    绝对不是最快的方法(如果性能很重要,直接使用Mmap.mmap,就像 DanGetz 在评论中建议的那样),但似乎这是最简单的方法,并且输出文件是人类可读的。

    【讨论】:

    • 但是 readdlmwritedlmHDF5 慢 1 到 2 个数量级...
    最近更新 更多