Stuck with another allocation question

The following program

``````using BenchmarkTools

const Example1 = Tuple{Int, Float64, Float64, Float64}
const Example2 = Tuple{Int, Float64}

function init!(v, n)
for i in 1:n
push!(v, (i, rand(), rand(), rand()))
end
end

function where(v, f::F) where F <: Function
ans = similar(v, eltype(v), 0)
for i in 1:length(v)
if f(v[i])
push!(ans, v[i])
end
end
ans
end

function select(v, R, f::F) where F <: Function
ans = similar(v, R, 0)
for i in 1:length(v)
push!(ans, f(v[i]))
end
ans
end

function test1()
v1 = Vector{Example1}()
init!(v1, 100000)
v2 = where(v1, e -> e[2] > 0.5 && e[3] > 0.5 && e[4] > 0.5)
end

function test2()
v1 = Vector{Example1}()
init!(v1, 100000)
v2 = where(v1, e -> e[2] > 0.5 && e[3] > 0.5 && e[4] > 0.5)
v3 = select(v2, Example2, e -> (e[1], e[2] * e[3] * e[4]))
end

@btime test1()
@btime test2()
``````

shows

``````  2.984 ms (31 allocations: 6.00 MiB)
3.311 ms (12195 allocations: 6.87 MiB)
``````

in the console. Juno profiler shows allocations at `push!(ans, f(v[i]))` in `select`. I have checked the types in `select` with

``````println(typeof(ans), " ", typeof(f(v[i])), " ", Base.allocatedinline(typeof(f(v[i]))))
``````

showing

``````Vector{Tuple{Int64, Float64}} Tuple{Int64, Float64} true
``````

`@code_warntype` for `select` also looks unsuspicious too me:

``````Variables
#self#::Core.Const(select)
v::Vector{Tuple{Int64, Float64, Float64, Float64}}
R::Core.Const(Tuple{Int64, Float64})
f::Core.Const(var"#49#50"())
@_5::Union{Nothing, Tuple{Int64, Int64}}
ans::Vector{Tuple{Int64, Float64}}
i::Int64

Body::Vector{Tuple{Int64, Float64}}
1 โ       (ans = Main.similar(v, R, 0))
โ   %2  = Main.length(v)::Int64
โ   %3  = (1:%2)::Core.PartialStruct(UnitRange{Int64}, Any[Core.Const(1), Int64])
โ         (@_5 = Base.iterate(%3))
โ   %5  = (@_5 === nothing)::Bool
โ   %6  = Base.not_int(%5)::Bool
โโโ       goto #4 if not %6
2 โ %8  = @_5::Tuple{Int64, Int64}::Tuple{Int64, Int64}
โ         (i = Core.getfield(%8, 1))
โ   %10 = Core.getfield(%8, 2)::Int64
โ   %11 = ans::Vector{Tuple{Int64, Float64}}
โ   %12 = Base.getindex(v, i)::Tuple{Int64, Float64, Float64, Float64}
โ   %13 = (f)(%12)::Tuple{Int64, Float64}
โ         Main.push!(%11, %13)
โ         (@_5 = Base.iterate(%3, %10))
โ   %16 = (@_5 === nothing)::Bool
โ   %17 = Base.not_int(%16)::Bool
โโโ       goto #4 if not %17
3 โ       goto #2
4 โ       return ans
``````

Now Iโm stuck understanding the reason for these allocations. What am I missing?

See this entry in the performance tips: Performance Tips ยท The Julia Language.

To force specialization on the type, use:

``````function select(v, ::Type{R}, f::F) where F <: Function where R
ans = similar(v, R, 0)
for i in 1:length(v)
push!(ans, f(v[i]))
end
ans
end
``````
3 Likes

Excellent. This solved the problem, thank you very much!