Julia:将CSV读入向量{T}有效/类型稳定性

Julia:将CSV读入向量{T}有效/类型稳定性,julia,Julia,我有许多非常大的CSV文件,我想将其解析为自定义数据结构,以便后续处理。我目前的方法包括CSV.File,然后将每个CSV.Row转换为自定义数据结构。它适用于小测试用例,但对于大文件来说效率非常低(GC非常高)。问题在第二步,我怀疑是由于类型不稳定。下面我将提供一个模拟示例 (我是朱莉娅的新朋友,如果我误解了什么,请道歉) 定义数据结构和转换逻辑: using CSV struct Foo a::Int32 b::Float32 end Foo(csv_row::CSV.R

我有许多非常大的CSV文件,我想将其解析为自定义数据结构,以便后续处理。我目前的方法包括
CSV.File
,然后将每个
CSV.Row
转换为自定义数据结构。它适用于小测试用例,但对于大文件来说效率非常低(GC非常高)。问题在第二步,我怀疑是由于类型不稳定。下面我将提供一个模拟示例

(我是朱莉娅的新朋友,如果我误解了什么,请道歉)

定义数据结构和转换逻辑:

using CSV

struct Foo
    a::Int32
    b::Float32
end

Foo(csv_row::CSV.Row) = Foo(csv_row.a, csv_row.b)
使用默认构造函数会导致0次分配:

julia> @allocated foo1 = Foo(1, 2.5)
0
但是,当从
CSV.Row
创建对象时,突然分配了80个字节:

julia> data = CSV.File(Vector{UInt8}("a,b\n1,2.5"); threaded = false)
1-element CSV.File{false}:
 CSV.Row: (a = 1, b = 2.5f0)

julia> @allocated foo2 = Foo(data[1])
80
在第一种情况下,所有类型都是稳定的:

julia> @code_warntype Foo(1, 2)
Variables
  #self#::Core.Compiler.Const(Foo, false)
  a::Int64
  b::Int64

Body::Foo
1 ─ %1 = Main.Foo::Core.Compiler.Const(Foo, false)
│   %2 = Core.fieldtype(%1, 1)::Core.Compiler.Const(Int32, false)
│   %3 = Base.convert(%2, a)::Int32
│   %4 = Core.fieldtype(%1, 2)::Core.Compiler.Const(Float32, false)
│   %5 = Base.convert(%4, b)::Float32
│   %6 = %new(%1, %3, %5)::Foo
└──      return %6
而在第二种情况下,它们不是:

julia> @code_warntype Foo(data[1])
Variables
  #self#::Core.Compiler.Const(Foo, false)
  csv_row::CSV.Row

Body::Foo
1 ─ %1 = Base.getproperty(csv_row, :a)::Any
│   %2 = Base.getproperty(csv_row, :b)::Any
│   %3 = Main.Foo(%1, %2)::Foo
└──      return %3
所以我想我的问题是:如何使第二个案例类型稳定并避免分配


顺便说一句,在
CSV.File
中显式提供类型并没有什么区别。

虽然这并不注重类型的稳定性,但我希望以下代码能够提供最高的性能和灵活性:

d = DataFrame!(CSV.File(Vector{UInt8}("a,b\n1,2.5\n3,4.0"); threaded = false))
上述方法可以有效地将
CSV.File
转换为类型稳定的结构,从而避免在此过程中复制数据。这应该适用于大型CSV文件的情况

现在:

julia> Foo.(d.a, d.b)
2-element Array{Foo,1}:
 Foo(1, 2.5f0)
 Foo(3, 4.0f0)

谢谢,这确实快多了。理解为什么在第二种情况下分配了80字节,而在第一种情况下分配了0字节仍然很有趣。