Reputation: 267
Something like this works
struct MyStruct
x
y
z
end
x = MyStruct(1, 2, 3)
a, b, c = ntuple(i -> getfield(x, fieldnames(MyStruct)[i]), length(fieldnames(MyStruct)))
but I can't help but think I'm just reinventing the wheel.
Upvotes: 1
Views: 290
Reputation: 4505
In addition to the existing answers, there are two alternatives: ntuple
and generated functions. A baseline is to manually write the code (which could be generated by a macro).
ntuple
usually generates better code, and in this case is more than 20 times faster.
struct Foo{A, B}
a::A
b::B
end
dump0(x) = getfield.(Ref(x), fieldnames(typeof(x)))
dump1(x) = ntuple(i -> getfield(x, i), fieldcount(typeof(x)))
# Based off https://discourse.julialang.org/t/slowness-of-fieldnames-and-propertynames/55364/2
@generated function dump2(obj::T) where {T}
return :((tuple($((
:(getfield(obj, $i)) for i in 1:fieldcount(obj)
)...))))
end
# hardcoded
dump3(x::Foo) = (x.a, x.b)
@code_llvm dump3(Foo(1,2))
and @code_llvm dump2(Foo(1,2))
are the same, whereas @code_llvm dump1(Foo(1,2))
and @code_llvm dump0(Foo(1,2))
are much complicated.
To get timings, define
eq0(a, b) = ==(dump0(a), dump0(b))
eq1(a, b) = ==(dump1(a), dump1(b))
eq2(a, b) = ==(dump2(a), dump2(b))
eq3(a, b) = ==(dump3(a), dump3(b))
(These equality functions will behave differently than the default defined on Foo
with respect to missing
and NaN
. Another motivation for dumping to a tuple is to define e.g. hash(x::Foo, s::UInt) = hash(dump(x), hash(Foo, s))
I was not able to find a performance benchmark that shows a considerable difference between dump1
and the dump2
, even though the code generated for dump2
is seemingly more efficient. dump0
is much slower:
julia> b = @benchmarkable eq0(Foo(a, b), Foo(c, d)) setup=((a, b, c, d) = rand(0:5, 4))
Benchmark(evals=1, seconds=5.0, samples=10000)
julia> run(b)
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
Range (min … max): 1.217 μs … 46.291 μs ┊ GC (min … max): 0.00% … 0.00%
Time (median): 1.269 μs ┊ GC (median): 0.00%
Time (mean ± σ): 1.417 μs ± 1.145 μs ┊ GC (mean ± σ): 0.00% ± 0.00%
[...]
Memory estimate: 448 bytes, allocs estimate: 14.
julia> b = @benchmarkable eq1(Foo(a, b), Foo(c, d)) setup=((a, b, c, d) = rand(0:5, 4))
Benchmark(evals=1, seconds=5.0, samples=10000)
julia> run(b)
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
Range (min … max): 37.000 ns … 18.596 μs ┊ GC (min … max): 0.00% … 0.00%
Time (median): 41.000 ns ┊ GC (median): 0.00%
Time (mean ± σ): 43.594 ns ± 185.778 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
[...]
Memory estimate: 0 bytes, allocs estimate: 0.
julia> b = @benchmarkable eq2(Foo(a, b), Foo(c, d)) setup=((a, b, c, d) = rand(0:5, 4))
Benchmark(evals=1, seconds=5.0, samples=10000)
julia> run(b)
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
Range (min … max): 40.000 ns … 13.651 μs ┊ GC (min … max): 0.00% … 0.00%
Time (median): 44.000 ns ┊ GC (median): 0.00%
Time (mean ± σ): 46.200 ns ± 152.346 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
[...]
Memory estimate: 0 bytes, allocs estimate: 0.
julia> b = @benchmarkable eq3(Foo(a, b), Foo(c, d)) setup=((a, b, c, d) = rand(0:5, 4))
Benchmark(evals=1, seconds=5.0, samples=10000)
julia> run(b)
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
Range (min … max): 40.000 ns … 168.000 ns ┊ GC (min … max): 0.00% … 0.00%
Time (median): 44.000 ns ┊ GC (median): 0.00%
Time (mean ± σ): 43.878 ns ± 2.114 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
[...]
Memory estimate: 0 bytes, allocs estimate: 0.
Upvotes: 0
Reputation: 69879
You can apply getfield
over names of fields like this:
getfield.(Ref(x), fieldnames(typeof(x)))
You can also replace Ref(x)
with (x,)
or [x]
to protect x
against being broadcasted over. Here is an example how you could silently get a wrong result:
julia> using NamedArrays
julia> x = NamedArray(fill((array=1, dicts=2, dimnames=3),3))
3-element Named Array{NamedTuple{(:array, :dicts, :dimnames),Tuple{Int64,Int64,Int64}},1}
A │
───┼─────────────────────────────────────
1 │ (array = 1, dicts = 2, dimnames = 3)
2 │ (array = 1, dicts = 2, dimnames = 3)
3 │ (array = 1, dicts = 2, dimnames = 3)
julia> getfield.(Ref(x), fieldnames(typeof(x))) # correct
(NamedTuple{(:array, :dicts, :dimnames),Tuple{Int64,Int64,Int64}}[(array = 1, dicts = 2, dimnames = 3), (array = 1, dicts = 2, dimnames = 3), (array = 1, dicts = 2, dimnames = 3)], (OrderedCollections.OrderedDict("1"=>1,"2"=>2,"3"=>3),), (:A,))
julia> getfield.(x, fieldnames(typeof(x))) # wrong
3-element Named Array{Int64,1}
A │
───┼──
1 │ 1
2 │ 2
3 │ 3
Upvotes: 2