[SOLVED] Julia: Abstract types vs Union of types

Issue

I am quite new to Julia, and I still have some doubts on which style is better when trying to do certain things… For instance, I have lots of doubts on the performance or style differences of using Abstract types vs defining Unions.

An example: Let’s imagine we want to implement several types of units (Mob, Knight, …) which should share most (if not all) of their attributes and most (if not all) of their methods.
I see two options to provide structure: First, one could declare an abstract type AbstractUnit from which the other types derive from, and then create methods for the abstract type. It would look something like this:

abstract type AbstractUnit end

mutable struct Knight <: AbstractUnit
    id     :: Int
    [...]
end

mutable struct Peasant <: AbstractUnit
    id     :: Int
    [...]
end

id(u::T)      where T <: AbstractUnit = u.id
[...]

Alternatively, one could define a union of types and create methods for the union. It would look something like this:

mutable struct Knight
    id     :: Int
    [...]
end

mutable struct Peasant
    id     :: Int
    [...]
end

const Unit = Union{Knight,Peasant}

id(u::Unit) = u.id
[...]

I understand some of the conceptual differences between these two approaches, and think the first one is more expandable. However, I have a lot of doubts in terms of performance. For instance, how bad would it be creating arrays of the AbstractUnit vs arrays of the union type in terms of memory allocation at runtime?

Thanks!

Solution

Absolutely use AbstractUnit instead of Unit in your methods’ argument type annotations. Unions are abstract types too, actually, but as you pointed out, you can’t add new types to it. In either case, the method is compiled to a specialization for each concrete type like Knight or Peasant, so your methods’ performance won’t be different.

As for Arrays’ element type parameters, there is the isbits Union optimization, but as the name suggests it only works if all the types in your Union are isbitstypes (no pointers, immutable). Your structs are mutable so that’s already not applicable. See, memory access is faster when instances are directly stored in Arrays, and the element type parameter (T in Vector{T}) must be a concrete isbitstype to allow this. When the element type parameter is abstract or mutable, generally Arrays only directly store pointers to the actual instances because multiple or mutable concrete types can have unknown and varying memory size. If the abstract type is an isbits Union though, instances could be stored directly in the Array: enough memory is allocated per element to contain the largest concrete type in the Union as well as a tag byte for each element specifying its type. A byte only has 256 values, so presumably this only works for Unions of at most 256 concrete types.

Another possible optimization by using a Vector{Unit} over Vector{AbstractUnit} is Union-splitting a type instability. I’m really not going to be able to make an example method that explains it better than the linked blog, so I’ll just give the short version. When Julia’s compiler fails to infer the type of a variable in your method at all (::Any annotations in @code_warntype), inner method calls involving the variable must do type checks and dispatch (selecting the specialization) at run-time, which can cost significant time. However, if Julia’s compiler can infer the variable to be a Union of a few concrete types (in practice, at most 4), conditional branches for each type can be used to eliminate most type checks and do dispatch at compile-time. Vector{AbstractUnit} can contain any number of types <: AbstractUnit, so the compiler cannot use Union-splitting. Vector{Unit} however lets the compiler know the elements must be either Knight or Peasant, which allows Union-splitting.

P.S. This is often a source of confusion for beginners, but while Unit is an abstract type, Vector{Unit} is a concrete type, just with an abstract type parameter. After all, it can have instances, all Arrays directly containing pointers to Knight or Peasant instances.

Answered By – BatWannaBe

Answer Checked By – Mary Flores (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *