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.
>