Getting different allocations from `@allocations` vs `@timev`/`@time`

I have a function f() = Int[].

Let’s try and check how much it allocates using @timev (or @time) and @allocations.

julia> @timev f()
  0.000002 seconds (1 allocation: 64 bytes)
elapsed time (ns):  1667
gc time (ns):       0
bytes allocated:    64
pool allocs:        1
non-pool GC allocs: 0
minor collections:  0
full collections:   0
Int64[]
julia> @allocations f()
0

See the “pool allocs” bit in the result from @timev. I think @allocations is supposed to count it too. But I was getting different number of allocations.

You can also try:

@timev          Array{Int64}(undef)
@allocations    Array{Int64}(undef)
@timev          Core.Box()
@allocations    Core.Box()
@timev          Ref{Int64}()
@allocations    Ref{Int64}()

So I complained here: `@allocated` and `@allocations` are giving the wrong result. · Issue #56705 · JuliaLang/julia · GitHub

I got the response:

@time returns the result. @allocated does not, so the system didn’t need to allocate it.

The same “does not return” logic applies to @allocations according to its documentation, so I will stick to it as an example.

Why should @allocations tell me that f() or Array{Int64}(undef) does not allocate?

This is the definition of @allocations:

macro allocations(ex)
    quote
        Experimental.@force_compile
        local stats = Base.gc_num()
        $(esc(ex))
        local diff = Base.GC_Diff(Base.gc_num(), stats)
        Base.gc_alloc_count(diff)
    end
end

I created two alternative versions, but this time. @alloc1 returns only the number of allocations, just like @allocations, but @alloc2 returns the return value of f() as well:

macro allocs1(ex)
    quote
        Base.Experimental.@force_compile
        local stats = Base.gc_num()
        local val = $(esc(ex))
        local diff = Base.GC_Diff(Base.gc_num(), stats)
        local count = Base.gc_alloc_count(diff)
        (count,)
    end
end

macro allocs2(ex)
    quote
        Base.Experimental.@force_compile
        local stats = Base.gc_num()
        local val = $(esc(ex))
        local diff = Base.GC_Diff(Base.gc_num(), stats)
        local count = Base.gc_alloc_count(diff)
        (count, val)
    end
end

@allocs1 f() gives the same result as @allocations f(), but @allocs2 f() does not. Is this right? Is Julia shuffling the order of execution?

This seems to be coming from differences in pool allocation when the return value of f() is returned:

function stats1()
    a1 = Base.gc_num().poolalloc
    val = f()
    a2 = Base.gc_num().poolalloc
    (a2 - a1,)
end
function stats2()
    a1 = Base.gc_num().poolalloc
    val = f()
    a2 = Base.gc_num().poolalloc
    (a2 - a1, val)
end

then:

julia> stats1()
(0,)

julia> stats2()
(1, Int64[])