Hello all. I’ve been struggling now for a good while with a performance issue that seems it should be quite trivial to resolve, but so far, an elegant solution has eluded me.
So, I have an object t::Table{T}
which lazily loads (from unstructured, untyped IO) the fields of an instance of T
. I want to construct from this an instance of T
. My thinking is that, if T
is mutable, this should be a no-op.
I would like to do this with
(t::Table{T})() where {T} = T((getproperty(t, n) for n ∈ fieldnames(T))...)
however, this performs rather badly:
julia> @btime $t()
14.923 μs (93 allocations: 5.48 KiB)
On the other hand, if I do
function (t::Table{T})() where {T}
m = T() # this is definitely wasting its time on defaults
for s ∈ fieldnames(T)
setfield!(m, s, getproperty(t, s))
end
m
end
I do somewhat better
julia> @btime $t()
11.636 μs (69 allocations: 4.28 KiB)
which is a little surprising especially because I don’t even have an appropriate constructor for this (T()
is definitely doing some unnecessary allocations).
I have often been confused about the performance consequences of splatting in the past. I have often been surprised with it not seeming to cost me anything, but in other cases like this, it’s seemingly disastrous.
So, what’s the best way to declare this constructor? Right now the best I could do would be to define a T(undef)
that does as little as possible and then set the fields afterward.