Allocations in a copy! construct

memory-allocation

#1

Consider the following variations of in-place copying:

lower = 1.:3
upper = 12.:16

function a()
    points = zeros(100)
    allocs = @allocated copy!(points, [lower[1:end-1]; upper[1:end-1]])
    println("case a: $allocs bytes")
    resize!(points, length(lower)+length(upper)-2)
end

function b()
    points = zeros(100)
    allocs = @allocated begin       
            copy!(points, lower[1:end-1])
            copy!(points, length(lower), upper[1:end-1])
            end
    println("case b: $allocs bytes")
    resize!(points, length(lower)+length(upper)-2)    
end
        
function c()
    points = zeros(100)
    allocs = @allocated begin
            copy!(points, @view(lower[1:end-1]))
            copy!(points, length(lower), @view(upper[1:end-1]))
            end
    println("case c: $allocs bytes")
    resize!(points, length(lower)+length(upper)-2)
end

The result that i get (in the second run), is:

julia> a(); b(); c();
case a: 320 bytes
case b: 192 bytes
case c: 1728 bytes
  • In case a, does [lower[1:end-1]; upper[1:end-1]] as a second argument to copy! create a temporary vector, while in case b it doesn’t, explaining that they differ roughly in twice as many allocations?
  • For case c, why does it allocate that much, shouldn’t it be the same as case b?

#2

This is the global scope problems. If you declare lower and upper as const, only a() allocates.


#3

Huh, i thought that enclosing the @allocated macros inside the function was enough. Thanks for the tip!!

Now cases b and c allocate exactly 0 bytes, now i see that’s the behavior to be expected there.


#4

Yes, global variables are evil (hopefully they’ll get optimized a bit at some point). There’s a trick though : it only does not allocate because lower and upper are ranges. If they are arrays (with collect() in front of the ranges) then b and c allocate. b has to allocate because indexing is a copy, the fact that c allocates is a bug (I think it’s on the devs radar, but low priority)


#5

Ok, interesting. Then copy!(points, lower[1:end-1]) didn’t create temporaries just because lower is a lazy array, i overlooked that. Trying with arrays behaves as you say, case c allocating less than b (but not exactly zero).