Optimization Controls and Optimization Hints
Static heuristics in Compilers recognize pattern in program structure such as loops, asserts, throwing an exception to judge execution frequency of code sections. These execution frequency estimates are used to make trade-offs in code size vs optimization aggressiveness judgements.
To deal with cases where static compiler heuristics fail programming languages can provide optimization controls and/or hints to give code authors more control over when an optimization should happen or to communicate properties useful for a compiler’s optimization heuristic. (e.g whether a region of code of code might be more/less frequently executed).
Furthermore, optimization controls guarantee an optimization happens regardless of possibly changing compiler heuristics between compiler versions making them important tool for predictable code optimization.
I think it would be good to push on adding controls and hints as part of the language and would like the community’s input on how that should look like.
I would like to start with seeding some, in my opinion, high value annotations.
Swift provides some controls and hints today. Some of them are fully supported, for example the optimization control @inline(never)
. The dual @inline(__always)
is underscored and therefore not consider to be “supported”. Some of them are only available in an underscored form, for example the optimization hint _onFastPath()
that should indicate to the inlining heuristic that a path containing a function call to this function is hot/frequently executed.
I would like to propose to make some optimization controls and hints more widely available in a supported form for when a predictable optimization (“optimization controls
) and influence on static compiler heuristics (“optimization hints”) are desired.
I think it useful to think about optimization annotations along several axis. One axis we already mentioned: optimization control versus optimization hint. A second axis is how declare the behavior/the scope: declaration site vs use site. A third useful axis is how far the annotation reaches: the immediate annotated object/function or transitively reachable objects/functions.
Optimization control/Optimization hint
An optimization control is a directive to the compiler to perform an optimization.
@inline(always)
/@inline(never)
are examples of optimization controls: the inlining optimization is guaranteed to always happen/never happen. When an optimization control is applied it should be a compiler error if it can’t be followed.
An optimization hint is an annotation that conveys information that influence compiler heuristics.
_onFastPath()
is an optimization hint that tells compiler heuristics that this call is situated on a frequently executed path. Optimizations that use notions of how frequently execute a path is can take this hint into consideration.
Declaration site versus use site annotation
An annotation can be a declaration site annotation such as @inline(never)
applying to all instances of a declaration: we want the annotated function never to be inlined regardless of use site properties. Or it can be a use site annotation such as the existing _onFastPath()
, when we want the annotation to apply to only to a specific context inside of a function.
func context() {
if cond {
_onFastPath()
frequentlyExecuted()
}
notExpectedToBeFrequentlyExecuted()
}
In the context of thinking about inlining we can form a matrix of possible useful options.
Use site | Declaration site | |
---|---|---|
Optimization control | inline { call() } |
@inline(always) func g() {} |
Optimization hint | frequentlyExecuted { call() } |
@inline(ifNotCold) func g() {} |
The inline { }
use site control specifies that all calls inside the braces are supposed to be inlined, this request for inlining only applies to one level of referenced functions.
The dual @inline(always)
site optimization control specifies that the function g()
should always be inlined.
The use side optimization hint frequentlyExecuted{}
specifies that the code inside the braces is to be consider “hot” by compiler heuristics such as the inlining heuristics.
The declaration site hint @inline(ifNotCold)
could be used by library authors to signal to the optimizer to only inline the function into regions not identified as cold according to its heuristics.
Optimization controls
There are several important classes of possible optimization controls: inlining, specialization, and constant propagation. I would like to focus on the first two, there is on going active work on constant propagation in the community: https://forums.swift.org/t/pitch-3-swift-compile-time-values.
Inlining controls
I think there are two important declaration site optimization controls and one use site control
-
Always inline this function into the caller
@inline(always)
; I think this optimization control should apply at all optimization modes (optimized, debug).
This control should either cause a compiler error if the annotated function is not also@inlinable
or should imply@inlinable
.- We could have a variant that restricts this to only optimized mode:
@inline(release)
- Or a variant that restricts the applicability only to the local module
@inline(onlyInModule)
- We could have a variant that restricts this to only optimized mode:
-
Never inline this function (
@inline(never)
), this control already exists. -
A use site
inline { call() }
annotation describing that the immediate call sites within the braces must be inlined. If immediatecall()
s inside the braces cannot be inlined because their body is not available this should be a hard compiler error.- It would make sense to add the dual
neverInline { call() }
- It would make sense to add the dual
Specialization controls
I think the following specialization controls would be useful.
-
A use site control to specialize all transitive generic call sites reachable from the braces
specialize { callSite<Int, SomeType>() }
with a dependence on the types in the substitution maps of the immediate generic call sitesfunc callSite<T , V>
.This should force specialization in both debug and release modes and should error if specialization is not possible.
Example
func a() {
specialize {
g<Int>()
}
}
func g<T>() {
t<T.Assoc>() // error if not specializable
f() // does not descend into non-generic calls
c<Int> // not an error if not specialzed because substitution map does not derive from T
}
-
Specialize a function in the library and provide an ABI entry point
@specialized(exported: true where T == Int) func g {}
. This attribute exists as a prototype in the form of@_specialize(exported: true,…)
. -
Create a specialized dispatch inside the function. This proposal was excepted as
@specialized(T==Int
), https://github.com/swiftlang/swift-evolution/blob/main/proposals/0460-specialized.md)
Optimization hints
Optimization hints function to influence compiler heuristics in a way to make existing heuristics more predictable and to provide further information to the heuristics.
Execution frequency
Communicating execution frequency where existing compiler heuristics don’t find a pattern to queue of is important.
frequentlyExecuted { callSite() }
This use site annotation would inform the compiler that this code within the braces is expected to execute many times. It would provide a general hint to various optimizations to strongly favor performance over code size for such sections. This might include more aggressive loop unrolling, vectorization, and inlining. A version of this currently exists as a call to the library function _onFastPath()
.
Inlining
A @inline(ifNotCold)
declaration site hint and maybe also a use site version inlineIfNotCold {}
. The intention is to inline the annotated function/code if the compiler heuristics determines the call is on a path that is determined to not be “cold”.
Having listed some optimization attributes I think would be useful to add, what are your thoughts and requirements?