<function>
or <operator>
).
This is just notation, and the symbols <
and >
should not be misconstrued as Julia's syntax.
Action | Keyboard Shortcut |
---|---|
Previous Section | Ctrl + 🠘 |
Next Section | Ctrl + 🠚 |
List of Sections | Ctrl + z |
List of Subsections | Ctrl + x |
Close Any Popped Up Window (like this one) | Esc |
Open All Codes and Outputs in a Post | Alt + 🠛 |
Close All Codes and Outputs in a Post | Alt + 🠙 |
Unit | Acronym | Measure in Seconds |
---|---|---|
Seconds | s | 1 |
Milliseconds | ms | 10-3 |
Microseconds | μs | 10-6 |
Nanoseconds | ns | 10-9 |
This section explores a technique for mitigating type instability that involves the so-called barrier functions. These are type-stable functions embedded within a type-unstable function. Their key to solving type instability lies the type inference of functions, which identifies any variable's type when passed as a function argument.
A salient aspect of barrier functions is that they can effectively address type instability, regardless of its underlying cause.
To better illustrate the technique, let's revisit a type-unstable function from a previous section. This defines a variable y
based on x
, and subsequently computes an operation involving y
.
function foo(x)
y = (x < 0) ? 0 : x
[y * i for i in 1:100]
end
@code_warntype foo(1) # type stable
@code_warntype foo(1.) # type UNSTABLE
The type instability in this example arises because 0
is Int64
, whereas x
could be either Int64
or a Float64
. In case x
is Int64
, y
will also be Int64
and so foo(1)
type stable. However, if x
is Float64
, the compiler is unable to determine whether y
will be Int64
or a Float64
. This turns foo(1.)
type unstable.
Addressing the type instability through a barrier functions requires embedding a type-stable function into foo
, with y
passed as one of its arguments. By doing so, the function will deduce y
's type, allowing the compiler to use this information for subsequent operations. The example below implements operation
as a barrier function. [note] Notice that there's an easier solution for this example, where 0
with zero(x)
. The function zero(x)
returns the null element for the type of x
.
operation(y) = [y * i for i in 1:100]
function foo(x)
y = (x < 0) ? 0 : x
operation(y)
end
@code_warntype operation(1) # barrier function is type stable
@code_warntype operation(1.) # barrier function is type stable
@code_warntype foo(1) # type stable
@code_warntype foo(1.) # barrier-function solution
In this version, the variable y
in foo(1.)
could still be an Int64
or Float64
. However, this becomes irrelevant, as operation(y)
will identify the type of y
before entering the array comprehension. Thus, [y * i for i in 1:100]
will be computed using a method specialized for the type of y
.
y
's type at each iteration and select a method accordingly.
For example, foo(1.)
in the code below is not applying the barrier-function technique properly: y
could be Float64
or Int64
, and operation(y,i)
only identifies the type inside the for-loop. This feature implies that the compiler must check y
's at each iteration of the for-loop.
operation(y,i) = y * i
function foo(x)
y = (x < 0) ? 0 : x
[operation(y,i) for i in 1:100]
end
@code_warntype foo(1) # type stable
@code_warntype foo(1.) # type UNSTABLE
The introduction of barrier functions can hinder the interpretation of @code_warntype
. This is because barrier functions typically mitigate the type instability, rather than completely eliminating it. Consequently, we may still receive a red warning.
To illustrate this, let's start presenting a scenario where the barrier function successfully handles the type instability.
x = ["a", 1] # variable with type 'Any'
function foo(x)
y = x[2]
[y * i for i in 1:100]
end
@code_warntype foo(x)
x = ["a", 1] # variable with type 'Any'
operation(y) = [y * i for i in 1:100]
function foo(x)
y = x[2]
operation(y)
end
@code_warntype foo(x)
The following example demonstrates that the barrier function can alleviate the impact of type instability, even without eliminating it. In the scenario considered, two type-unstable operations are present, and the barrier function targets the one with a more significant impact, which is given by the for-loop operation.
x = ["a", 1] # variable with type 'Any'
function foo(x)
y = 2 * x[2]
[y * i for i in 1:100]
end
@code_warntype foo(x)
x = ["a", 1] # variable with type 'Any'
operation(y) = [y * i for i in 1:100]
function foo(x)
y = 2 * x[2]
operation(y)
end
@code_warntype foo(x)
x = ["a", 1] # variable with type 'Any'
operation(y) = [y * i for i in 1:100]
function foo(z)
y = 2 * z
operation(y)
end
@code_warntype foo(x)