Avoiding allocations in a map over a tuple

Is there a way to avoid allocations in this function without using a generated function?

function foo(t::Tuple)
    map(t) do x
        isodd(x) ? 1 : 1.0
    end
end
julia> using BenchmarkTools

julia> t = (1, 2, 3, 4, 5, 6);

julia> @btime foo($t);
  142.019 ns (4 allocations: 112 bytes)
1 Like

My guess is that because foo is not type-stable, julia has to do some allocating.

In foo, the type of the output tuple depends on the values of the input tuple (the elements of the tuple being even or odd), rather than the type of the input tuple.

I’ve demonstrated this by writing bar–a similiar looking function that is type-stable–that when run, exhibits a performance gain with no allocating.

julia> using BenchmarkTools

julia> t = (1, 2, 3, 4, 5, 6);

julia> function foo(t::Tuple)
           map(t) do x
               isodd(x) ? 1 : 1.0
           end
       end

julia> @btime foo($t);
  109.042 ns (4 allocations: 112 bytes)

julia> function bar(t::Tuple)
           map(t) do x
               isodd(x) ? 2.0 : 1.0
           end
       end

julia> @btime bar($t);
  7.300 ns (0 allocations: 0 bytes)
3 Likes

One allocation when written as ifelse:

julia> @btime @. ifelse(isodd($t), 1, 1.0)
  90.356 ns (1 allocation: 64 bytes)
(1, 1.0, 1, 1.0, 1, 1.0)
3 Likes

Ah, right, thanks. I didn’t think that through very carefully. Here’s another example with no allocations:

function bar(t::Tuple)
    map(t) do x
        (x isa Int) ? 1 : 1.0
    end
end
julia> t = (2, 2.0, 2, 2.0, 2, 2.0);

julia> @btime bar($t);
  1.432 ns (0 allocations: 0 bytes)
2 Likes