Why does this allocate?

Consider the following code:

function foo(x)
    if x[1] > 0 
        (x, 1)
    else
        nothing
    end
end

It seems that this code has 1 allocation:

julia> bla = [1]
1-element Array{Int64,1}:
 1

julia> @time foo(bla)
  0.004369 seconds (1.32 k allocations: 98.988 KiB)
([1], 1)

julia> @time foo(bla)
  0.000003 seconds (1 allocation: 32 bytes)
([1], 1)

Why does this happen?
I am using Julia built from commit 39c278b728.

The allocation does not happen for either

function foo1(x)
    if x[1] > 0 
        (x, 1)
    else
        (x, 2)
    end
end

or

function foo2(x)
    if x[1] > 0 
        x
    else
        nothing
    end
end

Probably because the tuple ends up in global scope. If you do something like

julia> g(bla) = (foo(bla); nothing)
g (generic function with 1 method)

julia> @time g(bla)
  0.000002 seconds

it doesn’t allocate.

1 Like

Note that your code also allocates if we add @noinline to foo and @code_llvm foo(bla) shows that a call to jl_gc_pool_alloc is being generated.
Consider this slightly longer example:

struct Bla
    x::Int
end

@noinline function Base.iterate(iter::Bla, state)
    st, idx = state

    if idx <= length(st)
        return st[idx], (st, idx + 1)
    else
        return nothing
    end
end

@noinline function Base.iterate(iter::Bla)
    st = collect(1:iter.x)
    idx = 1

    return Base.iterate(iter, (st, idx))
end

function sumslow(bla::Bla)
    n = 0

    for x in bla
        n += x
    end

    return n
end

Base.IteratorSize(::Type{Bla}) = Base.SizeUnknown()

Base.eltype(::Type{Bla}) = Int

The @noinline is intentional since this is a reduced example and I do not want to force @inline for the project I am working on.
I get the following result:

julia> @btime sumslow(Bla(2^20))
  12.555 ms (1048578 allocations: 40.00 MiB)
549756338176

Without the @noinline, I get

julia> @btime sumslow(Bla(2^20))
  1.950 ms (2 allocations: 8.00 MiB)
549756338176

Does this happen due to an unrelated reason?

1 Like

In general, tuples that wrap heap-allocated objects might themselves have to be heap-allocated.

3 Likes

I thought so. I’m probably hitting a case where https://github.com/JuliaLang/julia/pull/33886 does not apply, right?

1 Like