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?