I have the following MWE for two methods: addUnits1
is parametric in the type of an input vector, while addUnits2
gives the parameters of the individual elements of the input vector.
struct Myunit{X,Y}
x::X
y::Y
end
function addUnits1(d::Vector,k::Int64,l::Int64)
tab = zeros(k,l)
for i in 1:length(d)
tab[d[i].y,d[i].x] += 1
end
tab
end
function addUnits2(d::Vector{Myunit{X,Y}},k::Int64,l::Int64) where {X,Y}
tab = zeros(k,l)
for i in 1:length(d)
tab[d[i].y,d[i].x] += 1
end
tab
end
First, I tested whether there was a performance difference given an array of parametric type Myunit
:
N = 2^20;
k = 3;
l = 100;
xs = rand(1:l,N);
ys = rand(1:k,N);
d = Myunit.(xs,ys);
@btime addUnits1(d,k,l);
@btime addUnits2(d,k,l);
1.147 ms (1 allocation: 2.50 KiB)
1.136 ms (1 allocation: 2.50 KiB)
As expected, the difference is negligible, and @code_warntype
output for both is identical since the compiler knows what Myunit
has without it being supplied to addUnits1
:
@code_warntype addUnits1(d,k,l)
Variables
#self#::Core.Compiler.Const(addUnits1, false)
d::Array{Myunit{Int64,Int64},1}
k::Int64
l::Int64
tab::Array{Float64,2}
@_6::Union{Nothing, Tuple{Int64,Int64}}
i::Int64
Body::Array{Float64,2}
1 β (tab = Main.zeros(k, l))
β %2 = Main.length(d)::Int64
β %3 = (1:%2)::Core.Compiler.PartialStruct(UnitRange{Int64}, Any[Core.Compiler.Const(1, false), Int64])
β (@_6 = Base.iterate(%3))
β %5 = (@_6 === nothing)::Bool
β %6 = Base.not_int(%5)::Bool
βββ goto #4 if not %6
2 β %8 = @_6::Tuple{Int64,Int64}::Tuple{Int64,Int64}
β (i = Core.getfield(%8, 1))
β %10 = Core.getfield(%8, 2)::Int64
β %11 = Base.getindex(d, i)::Myunit{Int64,Int64}
β %12 = Base.getproperty(%11, :y)::Int64
β %13 = Base.getindex(d, i)::Myunit{Int64,Int64}
β %14 = Base.getproperty(%13, :x)::Int64
β %15 = Base.getindex(tab, %12, %14)::Float64
β %16 = (%15 + 1)::Float64
β Base.setindex!(tab, %16, %12, %14)
β (@_6 = Base.iterate(%3, %10))
β %19 = (@_6 === nothing)::Bool
β %20 = Base.not_int(%19)::Bool
βββ goto #4 if not %20
3 β goto #2
4 β return tab
@code_warntype addUnits2(d,k,l)
Variables
#self#::Core.Compiler.Const(addUnits2, false)
d::Array{Myunit{Int64,Int64},1}
k::Int64
l::Int64
tab::Array{Float64,2}
@_6::Union{Nothing, Tuple{Int64,Int64}}
i::Int64
Body::Array{Float64,2}
1 β (tab = Main.zeros(k, l))
β %2 = Main.length(d)::Int64
β %3 = (1:%2)::Core.Compiler.PartialStruct(UnitRange{Int64}, Any[Core.Compiler.Const(1, false), Int64])
β (@_6 = Base.iterate(%3))
β %5 = (@_6 === nothing)::Bool
β %6 = Base.not_int(%5)::Bool
βββ goto #4 if not %6
2 β %8 = @_6::Tuple{Int64,Int64}::Tuple{Int64,Int64}
β (i = Core.getfield(%8, 1))
β %10 = Core.getfield(%8, 2)::Int64
β %11 = Base.getindex(d, i)::Myunit{Int64,Int64}
β %12 = Base.getproperty(%11, :y)::Int64
β %13 = Base.getindex(d, i)::Myunit{Int64,Int64}
β %14 = Base.getproperty(%13, :x)::Int64
β %15 = Base.getindex(tab, %12, %14)::Float64
β %16 = (%15 + 1)::Float64
β Base.setindex!(tab, %16, %12, %14)
β (@_6 = Base.iterate(%3, %10))
β %19 = (@_6 === nothing)::Bool
β %20 = Base.not_int(%19)::Bool
βββ goto #4 if not %20
3 β goto #2
4 β return tab
Now I expected that passing the same data to addUnits1
but without the known structure of Myunit
would decrease performance, but the following turned out to be the fastest of the three:
d2 = vec(mapslices(z->(x=z[1],y=z[2]),hcat(xs,ys)',dims=1));
@btime addUnits1(d2,k,l);
1.131 ms (1 allocation: 2.50 KiB)
@code_warntype addUnits1(d2,k,l)
Variables
#self#::Core.Compiler.Const(addUnits1, false)
d::Array{NamedTuple{(:x, :y),Tuple{Int64,Int64}},1}
k::Int64
l::Int64
tab::Array{Float64,2}
@_6::Union{Nothing, Tuple{Int64,Int64}}
i::Int64
Body::Array{Float64,2}
1 β (tab = Main.zeros(k, l))
β %2 = Main.length(d)::Int64
β %3 = (1:%2)::Core.Compiler.PartialStruct(UnitRange{Int64}, Any[Core.Compiler.Const(1, false), Int64])
β (@_6 = Base.iterate(%3))
β %5 = (@_6 === nothing)::Bool
β %6 = Base.not_int(%5)::Bool
βββ goto #4 if not %6
2 β %8 = @_6::Tuple{Int64,Int64}::Tuple{Int64,Int64}
β (i = Core.getfield(%8, 1))
β %10 = Core.getfield(%8, 2)::Int64
β %11 = Base.getindex(d, i)::NamedTuple{(:x, :y),Tuple{Int64,Int64}}
β %12 = Base.getproperty(%11, :y)::Int64
β %13 = Base.getindex(d, i)::NamedTuple{(:x, :y),Tuple{Int64,Int64}}
β %14 = Base.getproperty(%13, :x)::Int64
β %15 = Base.getindex(tab, %12, %14)::Float64
β %16 = (%15 + 1)::Float64
β Base.setindex!(tab, %16, %12, %14)
β (@_6 = Base.iterate(%3, %10))
β %19 = (@_6 === nothing)::Bool
β %20 = Base.not_int(%19)::Bool
βββ goto #4 if not %20
3 β goto #2
4 β return tab
Somehow this is not working as expected.
Can someone please comment/confirm/deny any of the following?
- parametric types are named tuples and all of the above do the same thing
- neither function is behaving as a parametric method for some reason
- this MWE is just not βdeep enoughβ to see any benefit from parametric types and methods
- something else I completely missed
-edit: corrected addUnits1
suggested by @jlapeyre