Why do `@allocated` and `Profile.Allocs.@profile` give different results?

The number of allocations reported by Profile.Allocs.@profile agrees with @allocations, but the total number of bytes allocated differs from @allocated for some reason:

using Profile.Allocs

f(n) = sum(2*collect(1:n))

function g(n)
    println(@allocations(f(n)), " allocs: ", @allocated(f(n)), " bytes")
    Allocs.clear()
    Allocs.@profile sample_rate = 1.0 f(n)
    allocs = Allocs.fetch().allocs
    b = sum(a -> a.size, allocs)
    println(length(allocs), " allocs: ", b, " bytes")
end

gives

julia> g(1)
4 allocs: 128 bytes
4 allocs: 112 bytes

julia> g(2)
4 allocs: 160 bytes
4 allocs: 128 bytes

julia> g(100)
4 allocs: 1856 bytes
4 allocs: 1696 bytes

What’s the reason for this discrepancy?

(Tested on Julia 1.11.8, 1.12.4 and 1.13.0-alpha2.)

1 Like

This is because Profile.Allocs.@profile returns the actual object size and @allocated returns the size it occupies in memory (Pool size)

Detailed technical answer, take the following input:

julia> g(1) 
4 allocs: 128 bytes
4 allocs: 112 bytes

julia> g(2)
4 allocs: 160 bytes
4 allocs: 128 bytes

julia> g(3)
4 allocs: 160 bytes
4 allocs: 144 bytes

Objects have to fit in a specific “pool” in memory, this is why @allocated reports the same memory for for g(2) and g(3) (160 bytes) as the result still fits in the same pool.

I also used CodeGlass to find out what was being allocated. It found:

In REPL function f(n):
Array{Int64,1} (always 32 bytes)
Int64[] (8*n bytes + 16 bytes for array header)

In arraymath.jl:21 function *(A::Int64, B::Array{Int64, 1})::Array{Int64, 1}
Array{ Int64,1} (always 32 bytes)
Int64[] (8*n bytes + 16 bytes for array header)

Julia uses the the following pool sizes that are relevant to this question (julia_internal.h:436)

// 16 pools at 8-byte spacing
// the 8-byte aligned pools are only used for Strings
16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136,

Since it is not a string, it must fit the Int64 bodies into 16 bytes aligned pools, so not in the 24 bytes pool but in the 32 bytes, so for example:


g(1) Allocations:
1x Array{Int64,1}   = 32 bytes = 32 bytes in pool
1x Array{Int64,1}   = 32 bytes = 32 bytes in pool
1x Int64[] = 8*1+16 = 24 bytes = 32 bytes in pool
1x Int64[] = 8*1+16 = 24 bytes = 32 bytes in pool
Total actual object size: 112 bytes (Reported by Profile.Allocs.@profile)
Total size in pool      : 128 bytes (Reported by @allocated)

I hope this was helpful :slight_smile:

1 Like