Generator slow when passed as argument

@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