@code_warntype does indeed show us a type-instability when passing the generator as an argument. It loses its ability to infer length.
Interestingly, this behavior extends to Tuple:
julia> @btime Tuple(map(identity, (1,2,3)))
2.100 ns (0 allocations: 0 bytes)
(1, 2, 3)
julia> @btime Tuple(x for x ∈ (1,2,3))
195.726 ns (2 allocations: 112 bytes)
(1, 2, 3)
julia> @btime Tuple(((x for x ∈ (1,2,3))...,))
2.100 ns (0 allocations: 0 bytes)
(1, 2, 3)
but not collect:
julia> @btime collect(map(identity, (1,2,3)))
37.298 ns (1 allocation: 80 bytes)
3-element Vector{Int64}:
1
2
3
julia> @btime collect(x for x ∈ (1,2,3))
38.182 ns (1 allocation: 80 bytes)
3-element Vector{Int64}:
1
2
3
julia> @btime collect(((x for x ∈ (1,2,3))...,))
37.298 ns (1 allocation: 80 bytes)
3-element Vector{Int64}:
1
2
3
For larger Tuples, though (length>31), the penalty of type inference loss from passing a generator goes away (and the penalty shifts toward the splatted generator):
code
julia> using BenchmarkTools, Plots
julia> begin
tests = BenchmarkGroup()
tests[:map] = BenchmarkGroup()
tests[:generator] = BenchmarkGroup()
tests[:splatted] = BenchmarkGroup()
for i=1:100 # this takes like ten minutes or so
tests[:map][i] = @benchmarkable Tuple(map(identity, xs)) setup=(xs=((1:$i)...,))
tests[:generator][i] = @benchmarkable Tuple(x for x ∈ xs) setup=(xs=((1:$i)...,))
tests[:splatted][i] = @benchmarkable Tuple(((x for x ∈ xs)...,)) setup=(xs=((1:$i)...,))
end
end
julia> begin
tune!(tests)
res = run(tests)
end;
julia> begin
plot(xlabel="tuple size", ylabel="min time [ns]")
plot!(sort([(i, minimum(res[:map])[i].time) for i=eachindex(res[:map])]), label=":map")
plot!(sort([(i, minimum(res[:generator])[i].time) for i=eachindex(res[:generator])]), label=":generator")
plot!(sort([(i, minimum(res[:splatted])[i].time) for i=eachindex(res[:splatted])]), label=":splatted")
end
Memory allocated:
julia> begin
plot(xlabel="tuple size", ylabel="memory [bytes]")
plot!(sort([(i, minimum(res[:map])[i].memory) for i=eachindex(res[:map])]), label=":map")
plot!(sort([(i, minimum(res[:generator])[i].memory) for i=eachindex(res[:generator])]), label=":generator")
plot!(sort([(i, minimum(res[:splatted])[i].memory) for i=eachindex(res[:splatted])]), label=":splatted")
end

