I illustrate my problem with an example. Say I work heavily with small permutations (elements of the
symmetric group) and want to multiply them as fast as possible.
A permutation of 1:n
is just given by giving the images of the integers 1
to n
as a Vector
so I could do
Perm=Vector{Int}
a=[5,4,3,2,1]
b=[2,1,4,3,5]
function mult(a::Perm,b::Perm)
r=similar(a)
@inbounds for (i,v) in enumerate(a) r[i]=b[v] end
r
end
and this works quite well:
julia> @btime mult($a,$b)
31.705 ns (1 allocation: 128 bytes)
5-element Array{Int64,1}:
5
3
4
1
2
However the problem is that I must write mult(a,b)
and not a*b
as I would like, because
contrary to what I could do in a language like C++, Perm
is just an alias and not a type by itself,
thus if I define
Base.:*(a::Perm,b::Perm)=...
I am doing an horrible type hijacking (defining multiplication on Vector{Int}
).
The only thing I know how to do in Julia is define a struct
with a field:
struct Perm
d::Vector{Int}
end
pa=Perm(a)
pb=Perm(b)
Now I can overload *
without problems
function Base.:*(a::Perm,b::Perm)
r=similar(a.d)
@inbounds for (i,v) in enumerate(a.d) r[i]=b.d[v] end
Perm(r)
end
but I am not as efficient: the multiplication does 2 allocations, since Julia allocates the struct
and
its field d
separately:
julia> @btime $pa*$pb
38.247 ns (2 allocations: 144 bytes)
Perm([5, 3, 4, 1, 2])
So my question is: can I avoid that? What is the best design in Julia?