Annotating the output of vcat seems to do the trick (these cat
functions tend to cause type-instabilities, because the type of the output is dependent on the input. There is some discussion on this in this recent thread):
julia> baz() = (vcat([randn(1,1,3) for i in 1:3]...)::Array{Float64,3},)
baz (generic function with 1 method)
julia> @code_warntype baz()
Variables
#self#::Core.Compiler.Const(baz, false)
#7::var"#7#8"
Body::Tuple{Array{Float64,3}}
1 ─ (#7 = %new(Main.:(var"#7#8")))
│ %2 = #7::Core.Compiler.Const(var"#7#8"(), false)
│ %3 = (1:3)::Core.Compiler.Const(1:3, false)
│ %4 = Base.Generator(%2, %3)::Core.Compiler.Const(Base.Generator{UnitRange{Int64},var"#7#8"}(var"#7#8"(), 1:3), false)
│ %5 = Base.collect(%4)::Array{Array{Float64,3},1}
│ %6 = Core._apply_iterate(Base.iterate, Main.vcat, %5)::Any
│ %7 = Core.apply_type(Main.Array, Main.Float64, 3)::Core.Compiler.Const(Array{Float64,3}, false)
│ %8 = Core.typeassert(%6, %7)::Array{Float64,3}
│ %9 = Core.tuple(%8)::Tuple{Array{Float64,3}}
└── return %9
Still, my preferred way to deal with such thing, if possible, would be to keep those transformations on the data outside the functions that are critical for performance, and pass the “final” data structure to those. In that case, even if there are type-instabilities in the “preparation” of the data, it will not propagate into the critical functions.
Edit: As pointed by @mcabbott , the problem here is the splatting, and is not of the same sort of that of the other thread. It would be the same if you used cat
, though, even without the splatting:
julia> baz() = (cat([randn(1,1,3) for i in 1:3],dims=2),)
baz (generic function with 1 method)
julia> @code_warntype baz()
Variables
#self#::Core.Compiler.Const(baz, false)
#23::var"#23#24"
Body::Tuple{Array} #<--- RED