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.acould 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.amust 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 <: Realmeans Union{Vector{T} for all T belong to Real}.- Unionall:
VectormeansVector{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
wherefor struct to put constraints. You directly put constraints in the{}. For example,struct S{T<:Real}.