Julia Performance Tips
Table of Contents
This is a note for https://github.com/AdvancedScientificComputingInJuliaWashU/Performance.jl
Global variable
- Avoid if possible
- If not, use a const Ref to get the type fixed
Type: T <: Union{T, …} <: Any
- If you do not specify a type, it is type Any
- A single concrete type is the best
- If not possible, a union of all possible types is still way better than Any.
- This is especially true if it is in a container such as Array.
Type declaration with abstract type
It is good for function arguments: reusable for multiple types.
- For example,
function f(a:Real)
means when you call it withf(1)
, af(a::Int64)
will be generated and compiled.
It is bad for fields in a struct
- The compiler uses the types of objects, not their values, to determine how to build code.
For the following example, in
S{Int64}
, ands.a
could beVector{Int64}
orSparseVector{Int64}
. As a result, a concrete type with an abstract field, e.g.S{Int64}
, feels like an abstract type itself. Imagine if you put it into a Vector,Vector{S{Int64}}
is likeVector{AbstractVector{Int64}}
. The memory layout of eachAbstractVector{Int64}
can be very different (for example, some are static, some are dynamic.). Therefore, it is hard to optimize.- If it is a mutable struct, you can even change
s.a
's type at runtime after creation. Therefore, the compiler has no way to optimize it.
struct S{T} a::AbstractVector{T} end
- If it is a mutable struct, you can even change
The good version is as follows. In a
S{Vector{Int}}
,s.a
must beVector{Int}
.struct S{T<:AbstractVector} a::T end
- The key is, make sure once the type of S is specified as concrete, all its fields are concrete as well.
- If you specify a field as an abstract type, the functions applies on it will likely return type: Any (Because anyone can subtype this abstract type and define a method for the function to return whatever type). Functions includes getindex. This meaning array[i] can return type Any, even if array::AbstractVector{Int64}. Although in the runtime, you can see the type being correct. However, @codewarntype or @inferred cannot infer the type while compiling.
Multiple Dispatch
- As a rule of thumb, compile time multiple dispatch is as fast as C/C++, Julia can do all the optimizations like inlining. However, runtime multiple dispatch needs to do a lookup and it is slow (can be slower than runtime if-else check)
- "When types can't be predicted in advance, you're better off doing more at runtime and less via the type system.
Dict vs. Array
- Hash table lookup can be a order of magnitude slower than Array lookup (even with some condiction check)
- If you are working with char, using Array as a dictionary.
where
parametric type:
Vector{<:Real}
orVector{T} where T <: Real
means Union{Vector{T} for all T belong to Real}.- Unionall:
Vector
meansVector{T} where T <: Any
method
function f(x::T) where {T}
just tells the parser, T is template/parametric.function f(x::T) where {T<:Real}
orfunction f(x::T where T<:Real)
can be used to put some constraint on the type.
struct
- On the other hand, there is no
where
for struct to put constraints. You directly put constraints in the{}
. For example,struct S{T<:Real}
.