Compilation and Type Inference
Let's continue with the same example, but adding several function calls.
If we've just started a new session, so that foo(1,2)
is called for the first time, Julia won't find any method instance for foo(a::Int64, b::Int64)
. Consequently, it'll have to generate a new implementation for this unique combination of types. Compiling code is the reason a function's first call is slower than the subsequent runs, a phenomenon that is dubbed Time To First Plot.
Note that this code is eventually stored, so that the computation of foo(3, 2)
will directly invoke the method instance foo(a::Int64, b::Int64)
. Nonetheless, the call foo(3.0, 2)
requires compiling a new method instance foo(a::Float64, b::Int64)
, resulting in a first-call latency again.
Type Inference
The compilation process follows a series of steps whose understanding is crucial for performance. To explain them, let's consider compilation for the method instance foo(a::Int64, b::Int64)
.
In this case, the compiler's task is to generate computer instructions to calculate 2 + a * b
. This process begins with what's known as type inference, where the compiler attempts to identify concrete types of all variables and expressions involved. In our example, this means inferring concrete types for 2
, a = 1
, and b = 2
.
Type inference establishes a crucial difference between executing operations in the global scope or through functions. In the former, Julia doesn't attempt to infer concrete types when it creates to code for computing the operations, thereby preventing specialization.
Since all variables are Int64
in the example considered, the compiler is capable of creating instructions specialized to variables of type Int64
. This is the essence of type stability, where the compiler identifies unique concrete types and specialization is possible.
Type stability is far from being guaranteed, with the compiler unable to determine a single concrete type. When this is the case, it must consider multiple possibilities, generating code for each potential type. This lack of specialization can degrade performance severely. For instance, this is what occurs below.
In the example, the compiler can't identify a unique concrete type for x[1]
and x[2]
. Consequently, it can't specialize the operation +
.