I ran the following benchmark to compare the cost of doing dispatch on closed unions with the cost of doing dispatch on abstract types:
using BenchmarkTools
abstract type SignedInteger end
struct Pos <: SignedInteger
abs :: UInt64
end
struct Neg <: SignedInteger
abs :: UInt64
end
const AnySignedInteger = Union{Pos, Neg}
const posvec = [Pos(i) for i in 1:100]
const negvec = [Neg(i) for i in 1:100]
value(x::Pos) = Int64(x.abs)
value(x::Neg) = -Int64(x.abs)
function test_open()
return sum(value(x) for x in SignedInteger[posvec; negvec])
end
function test_closed()
return sum(value(x) for x in AnySignedInteger[posvec; negvec])
end
println("Testing open version")
@btime test_open()
println("Testing closed version")
@btime test_closed()
The result:
Testing open version
1.792 μs (202 allocations: 4.92 KiB)
Testing closed version
697.020 ns (2 allocations: 2.02 KiB)
Conclusion: it is about 2.3x faster to do dispatch on a closed union type. I actually expected more of a difference, which makes me think that my naive solution would actually not be prohibitively slow compared to something smarter.