It seems concatenating, especially vectors, is quite common. And it would be nice if concatenating with mixed scalar and vector arguments was type-stable.
I did some playing around and although I don't fully understand the impact, I [think I] was able to get a type-stable version of vcat with vector/scalar arguments. Essentially widen signatures of vcat to allow mixed arguments and introduce full for Number: abstractarray.jl line 742 vcat(V::AbstractVector...) = typed_vcat(promote_eltype(V...), V...) vcat(V::Union{Number,AbstractVector}...) = typed_vcat(promote_eltype(V...), V...) abstractarray.jl line 745 function typed_vcat(T::Type, V::AbstractVector...) function typed_vcat(T::Type, V::Union{Number,AbstractVector}...) Base.full(x::Number) = [x] Here full acts as a sort of "promotion to vector". This might seem a somewhat twisted use of full, so in typed_cat, I tried substituting: a = similar(isa(V[1], AbstractVector) ? full(V[1]) : [V[1]], T, n) instead of a = similar(full(V[1]), T, n) but it didn't work. I don't really know how to try this out directly, so I overwrote methods: function Base.vcat(V::Union{Number,AbstractVector}...) #println("my vcat") Base.typed_vcat(Base.promote_eltype(V...), V...) end function Base.typed_vcat(T::Type, V::Union{Number,AbstractVector}...) #println("my typed_vcat") n::Int = 0 for Vk in V n += length(Vk) end a = similar(full(V[1]), T, n) pos = 1 for k=1:length(V) Vk = V[k] p1 = pos+length(Vk)-1 a[pos:p1] = Vk pos = p1+1 end a end function Base.full(x::Number) #println("my full") [x] end On Friday, April 22, 2016 at 3:32:27 AM UTC+10, Jeremy Kozdon wrote: > In a class I'm teaching the students are using Julia and I couldn't for > the life of me figure out why one of my students codes was allocating a lot > of memory. > > I finally paired it down the following example that I don't understand: > > function version1(N) > b = [1;zeros(N-1)] > println(typeof(b)) > for k = 1:N > for j = 1:N > b[j] += k > end > end > end > > > function version2(N) > b = zeros(N) > b[1] = 1 > println(typeof(b)) > for k = 1:N > for j = 1:N > b[j] += k > end > end > end > > N = 1000 > println("compiling..") > @time version1(N) > version2(N) > println() > println() > > println("Version 1") > @time version1(N) > println() > > println("Version 2") > @time version2(N) > > The output of this (without the compiling output) in v0.4.5 is: > > Version 1 > Array{Float64,1} > 0.092473 seconds (3.47 M allocations: 52.920 MB, 3.24% gc time) > > Version 2 > Array{Float64,1} > 0.001195 seconds (27 allocations: 8.828 KB) > > Both version produce the same type for Array b, but in version1 every time > through the loop allocation happens and in the version2 the only allocation > is of the initial array. > > I've not run into this one before (because I would never do version1), but > as all of us that teach know students will always surprise you with their > approaches. > > Any help understanding what's going on would be appreciated. >