Why is there an allocation?

module mbas001
elxs = fill(Matrix{Float64}(undef, 3, 3), 7)
for _i in eachindex(elxs)
    elxs[_i] = rand(3, 3)
end
ex = fill(zero(Float64), 3, 3) 
copy2ex!(ex, elxs, j, k) = let
    elxsc = elxs[c]
    for _i in axes(ex, 2)
        ex[1, _i] = elxsc[j, _i]; 
        ex[2, _i] = elxsc[k, _i]; 
    end
    ex
end
c = 4
@time let
    ex[1,:] = elxs[c][1,:]; ex[2,:] = elxs[c][2,:]; 
end
@time copy2ex!(ex, elxs, 1, 2)
@time copy2ex!(ex, elxs, 1, 2)
@time copy2ex!(ex, elxs, 1, 2)
end

produces

julia> include("C:\\Users\\pkonl\\Documents\\00WIP\\HelmBEM.jl\\test\\littletest.jl")                                                                       
WARNING: replacing module mbas001.                                            
  0.000023 seconds (6 allocations: 224 bytes)                                 
  0.018632 seconds (9.56 k allocations: 517.691 KiB, 99.73% compilation time) 
  0.000017 seconds (6 allocations: 96 bytes)                                  
  0.000016 seconds (6 allocations: 96 bytes)                                  
Main.mbas001  

What puzzles me is why the function copy2ex! allocates memory.
It happens in the loop

    for _i in axes(ex, 2)
        ex[1, _i] = elxsc[j, _i]; 
        ex[2, _i] = elxsc[k, _i]; 
    end

Which, in my understanding, should be simply assignment of one matrix location the value of another matrix location. In other words, scalars.

c is a non-const global variable.

3 Likes

Wow. I missed that! Thanks.

Now if I could understand why the memory allocation happens here:

ex[1,:] = elxs[c][1,:]; ex[2,:] = elxs[c][2,:];

There are still 6 ALLOCATIONS:

WARNING: replacing module mbas001.                                            
  0.000025 seconds (6 allocations: 224 bytes)                                 
  0.022149 seconds (6.53 k allocations: 337.683 KiB, 99.85% compilation time) 
  0.000015 seconds                                                            
  0.000003 seconds                                                            
Main.mbas001 

The code looks like this now:

module mbas001
elxs = fill(Matrix{Float64}(undef, 3, 3), 7)
for _i in eachindex(elxs)
    elxs[_i] = rand(3, 3)
end
ex = fill(zero(Float64), 3, 3) 
copy2ex!(ex, elxs, c, j, k) = let
    elxsc = elxs[c]
    for _i in axes(ex, 2)
        ex[1, _i] = elxsc[j, _i]; 
        ex[2, _i] = elxsc[k, _i]; 
    end
    ex
end
@time let
    c = 4
    ex[1,:] = elxs[c][1,:]; ex[2,:] = elxs[c][2,:]; 
end
c = 4
@time copy2ex!(ex, elxs, c, 1, 2)
@time copy2ex!(ex, elxs, c, 1, 2)
@time copy2ex!(ex, elxs, c, 1, 2)
end

const

const

@time @views let

Now everything is a local variable. Still, there are allocations.

module mbas001
let
elxs = fill(Matrix{Float64}(undef, 3, 3), 7)
for _i in eachindex(elxs)
    elxs[_i] = rand(3, 3)
end
ex = fill(zero(Float64), 3, 3) 
copy2ex!(ex, elxs, c, j, k) = let
    elxsc = elxs[c]
    for _i in axes(ex, 2)
        ex[1, _i] = elxsc[j, _i]; 
        ex[2, _i] = elxsc[k, _i]; 
    end
    ex
end
@time @views let
    c = 4
    ex[1,:] = elxs[c][1,:]; ex[2,:] = elxs[c][2,:]; 
end
c = 4
@time copy2ex!(ex, elxs, c, 1, 2)
@time copy2ex!(ex, elxs, c, 1, 2)
@time copy2ex!(ex, elxs, c, 1, 2)
end
end

gives

WARNING: replacing module mbas001.                                            
  0.000013 seconds (15 allocations: 512 bytes)                                
  0.020667 seconds (6.54 k allocations: 338.026 KiB, 99.88% compilation time) 
  0.000005 seconds (9 allocations: 352 bytes)                                 
  0.000003 seconds (9 allocations: 352 bytes)                                 
Main.mbas001 

Edit: Note that I forgot to remove the views. Everything is local now.

Doesn’t @time itself allocate? I always use @btime and variable interpolation for reliable allocation estimates.

It actually worked (sort of) in https://discourse.julialang.org/t/why-is-there-an-allocation/85405/4: there were zero allocations with the function.

@btime seems to have improved over time, but it might still be an idea to use BenchmarkTools to be more certain.

please implement all the changes I proposed:

julia> module mbas001
       const elxs = fill(Matrix{Float64}(undef, 3, 3), 7)
       for _i in eachindex(elxs)
           elxs[_i] = rand(3, 3)
       end
       const ex = fill(zero(Float64), 3, 3) 
       copy2ex!(ex, elxs, c, j, k) = let
           elxsc = elxs[c]
           for _i in axes(ex, 2)
               ex[1, _i] = elxsc[j, _i]; 
               ex[2, _i] = elxsc[k, _i]; 
           end
           ex
       end
       @time @views let
           c = 4
           ex[1,:] = elxs[c][1,:]; ex[2,:] = elxs[c][2,:]; 
       end
       c = 4
       @time copy2ex!(ex, elxs, c, 1, 2)
       @time copy2ex!(ex, elxs, c, 1, 2)
       @time copy2ex!(ex, elxs, c, 1, 2)
       end
  0.000001 seconds
  0.012696 seconds (7.42 k allocations: 388.971 KiB, 99.93% compilation time)
  0.000003 seconds
  0.000002 seconds
Main.mbas001

Thank you, I appreciate your suggestion, but really I want to have only local variables.
Testing in the global scope was obviously a mistake.

It is possible that @time allocates when it is used in a local scope. Tracking allocations
with everything in local scope:

        - module mbas001
        - let
        - elxs = fill(Matrix{Float64}(undef, 3, 3), 7)
        - for _i in eachindex(elxs)
        -     elxs[_i] = rand(3, 3)
        - end
        - ex = fill(zero(Float64), 3, 3) 
        - copy2ex!(ex, elxs, c, j, k) = let
        0     elxsc = elxs[c]
        0     for _i in axes(ex, 2)
        0         ex[1, _i] = elxsc[j, _i]; 
        0         ex[2, _i] = elxsc[k, _i]; 
        0     end
        0     ex
        - end
        - let
        -     c = 4
        -     ex[1,:] = elxs[c][1,:]; ex[2,:] = elxs[c][2,:]; 
        - end
        - c = 4
        - copy2ex!(ex, elxs, c, 1, 2)
        - copy2ex!(ex, elxs, c, 1, 2)
        - copy2ex!(ex, elxs, c, 1, 2)
        - end
        - end