How do I determine the memory allocations in Julia using @code_typed (or @code_llvm)

I am looking for a basic understanding of how allocations “work” in Julia.

See this code (obtained from another post I was reading).

 function sum_of_diff_1(x)
         sum(diff(x))
 end

A vectorized version of the sum of differences. Here is the same code using loops

Running

 @time sum_of_diff_1(rand(100))
  0.000007 seconds (9 allocations: 1.969 KiB)

So we see 9 allocations. Could someone explain to me where these 9 allocations are in @code_typed (or @code_warntype, @code_llvm) and exactly what it means?

Thanks

@time defines some variables which will here be in global scope and will thus be boxed (and allocated on the heap).

If you out the @time macro in a function, some of the allocations would go away.

1 Like

Sorry, I should be more clear. I am not asking why the allocations are happening, but “where” its happening in the compiled code. i.e. what are the commands in @code_typed or @code_llvm that allocate memory. I guess the example I have is a bad one but I don’t know of a good example.

I suppose I am asking someone to teach me how to read llvm and I am not at that level yet so I can revisit this question in a few months/years :stuck_out_tongue:

I don’t think you’ll see the allocations in that particular case. The reason is that @code_typed(sum(rand(100))) does, basically:

  1. Compute rand(100) (this allocates an array)
  2. Look up the type of the result, call it T
  3. Look up the appropriate specialized method for sum(::T) for that type T.

So the allocation (which happened inside the call to rand(100)) isn’t part of what @code_typed is showing you.

On the other hand, you can look at:

julia> @code_typed rand(100)
CodeInfo(
243 1 ── %1  = (Base.getfield)(dims, 1, true)::Int64                                                                                                                                       │╻╷╷      Type
    │    %2  = Random.GLOBAL_RNG::MersenneTwister                                                                                                                                          │╻        rand
    │    %3  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{Float64,1}, svec(Any, Int64), :(:ccall), 2, Array{Float64,1}, :(%1), :(%1)))::Array{Float64,1}                            ││╻╷╷      rand

which does show the jl_alloc_array_1d call. However, @code_whatever is only going to show you the given function and whatever was inlined into it. If the allocation happens to occur inside some function which was not inlined (which is very often the case), then the @code_whatever tools won’t necessarily show it:

julia> @noinline f() = rand(100)
f (generic function with 1 method)

julia> g() = f()
g (generic function with 1 method)

julia> @code_typed(g())
CodeInfo(
1 1 ─ %1 = invoke Main.f()::Array{Float64,1}                                                                                                                                                                            │
  └──      return %1                                                                                                                                                                                                    │
) => Array{Float64,1}

As other people have already explained, at least some of those allocations happen in the @time macro and the rand(100) construction. The easiest way to get rid of those distractions is to use BenchmarkTools and precompute the input to the function.

julia> using BenchmarkTools

julia> function sum_of_diff_1_loop(x)
           s = zero(eltype(x))
           for k = 2:length(x)
               s += x[k] - x[k - 1]
           end
           return s
       end
sum_of_diff_1_loop (generic function with 1 method)

julia> x = rand(100);

julia> @btime sum_of_diff_1_loop($x);
  80.965 ns (0 allocations: 0 bytes)

If this says 0 allocations you can stop looking for allocations.

As for the @code_whatever tools, if @code_warntype marks things red, frequently allocations will be needed to handle that (with the exception of some small unions in Julia 0.7 and up). In @code_llvm I only know to look for alloca, but allocations can easily be hidden in calls to non-inlined functions.

But this is relatively easy to experiment with. Write the smallest function you can that does some kind of allocation, and as little else as possible, and see what shows up in @code_whatever. If necessary, compare to what turns up in non-allocating small functions.