Consider a list of symbol and functions:
julia> macro big_closure_list(T, n)
esc(:($T[$([:($T(:x, ()->$(Symbol(:v, i)))) for i in 1:n]...)]))
end;
julia> @macroexpand @big_closure_list(Pair, 2)
:(Pair[Pair(:x, (()->(v1))),
Pair(:x, (()->(v2)))])
Performance of this expression depends a lot on the type I put in for the T:
ulia> @time @big_closure_list(Pair, 20);
0.161013 seconds (62.34 k allocations: 3.871 MiB, 98.59% compilation time)
julia> @time @big_closure_list(Pair{Symbol, Function}, 20);
0.082167 seconds (27.81 k allocations: 1.701 MiB, 97.93% compilation time)
julia> struct Foo
a::Symbol
b::Function
end
julia> @time @big_closure_list(Foo, 20);
0.000166 seconds (387 allocations: 26.516 KiB)
(all times are from a second run, for JIT-warm-up)
I can sorta-understand the first one being slower, with the type being abstract… Although I’d love to hear a detailed explanation. But the difference between Pair{Symbol, Function}
and Foo
is surprising to me. Aren’t these two supposed to be entirely equivalent? Furthermore, MyPair
is fast!
julia> struct MyPair{A,B}
a::A
b::B
end
julia> @time @big_closure_list(MyPair{Symbol, Function}, 20);
0.000163 seconds (387 allocations: 26.516 KiB)
What does Pair
do to cause that slow performance? It seems to be the inner constructor.
julia> struct MyPair2{A, B}
a::A
b::B
MyPair2{A, B}(a::A, b::B) where {A, B} = new(a, b)
end
julia> @time @big_closure_list(MyPair2{Symbol, Function}, 20);
0.084144 seconds (28.68 k allocations: 1.729 MiB, 98.69% compilation time)
Shouldn’t that constructor be equivalent to the automatic one from MyPair
?