-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
introduce
@noinfer
macro to tell the compiler to avoid excess infer…
…ence This commit introduces new compiler annotation named `@noinfer`, which requests the compiler to avoid excess inference. ## Understand `@nospecialize` In order to discuss `@noinfer`, it would help a lot to understand the behavior of `@nospecialize`. Its docstring says simply: > This is only a hint for the compiler to avoid excess code generation. More specifically, it works by _suppressing dispatches_ with complex runtime types of the annotated arguments. This could be understood with the example below: ```julia julia> function invokef(f, itr) local r = 0 r += f(itr[1]) r += f(itr[2]) r += f(itr[3]) r end; julia> _isa = isa; # just for the sake of explanation, global variable to prevent inling julia> f(a) = _isa(a, Function); julia> g(@nospecialize a) = _isa(a, Function); julia> dispatchonly = Any[sin, muladd, nothing]; # untyped container can cause excessive runtime dispatch julia> @code_typed invokef(f, dispatchonly) CodeInfo( 1 ─ %1 = π (0, Int64) │ %2 = Base.arrayref(true, itr, 1)::Any │ %3 = (f)(%2)::Any │ %4 = (%1 + %3)::Any │ %5 = Base.arrayref(true, itr, 2)::Any │ %6 = (f)(%5)::Any │ %7 = (%4 + %6)::Any │ %8 = Base.arrayref(true, itr, 3)::Any │ %9 = (f)(%8)::Any │ %10 = (%7 + %9)::Any └── return %10 ) => Any julia> @code_typed invokef(g, dispatchonly) CodeInfo( 1 ─ %1 = π (0, Int64) │ %2 = Base.arrayref(true, itr, 1)::Any │ %3 = invoke f(%2::Any)::Any │ %4 = (%1 + %3)::Any │ %5 = Base.arrayref(true, itr, 2)::Any │ %6 = invoke f(%5::Any)::Any │ %7 = (%4 + %6)::Any │ %8 = Base.arrayref(true, itr, 3)::Any │ %9 = invoke f(%8::Any)::Any │ %10 = (%7 + %9)::Any └── return %10 ) => Any ``` The calls of `f` remain to be `:call` expression (thus dispatched and compiled at runtime) while the calls of `g` are resolved as `:invoke` expressions. This is because `@nospecialize` requests the compiler to give up compiling `g` with concrete argument types but with precisely declared argument types, and in this way `invokef(g, dispatchonly)` will avoid runtime dispatches and accompanying JIT compilations (i.e. "excess code generation"). The problem here is, it influences dispatch only, does not intervene into inference in anyway. So there is still a possibility of "excess inference" when the compiler sees a considerable complexity of argument types during inference: ```julia julia> withinfernce = tuple(sin, muladd, "foo"); # typed container can cause excessive inference julia> @time @code_typed invokef(f, withinfernce); 0.000812 seconds (3.77 k allocations: 217.938 KiB, 94.34% compilation time) julia> @time @code_typed invokef(g, withinfernce); 0.000753 seconds (3.77 k allocations: 218.047 KiB, 92.42% compilation time) ``` The purpose of this PR is basically to provide a more drastic way to avoid excess compilation. ## Design Here are some ideas to implement the functionality: 1. make `@nospecialize` avoid inference also 2. add noinfer effect when `@nospecialize`d method is annotated as `@noinline` also 3. implement as `@pure`-like boolean annotation to request noinfer effect on top of `@nospecialize` 4. implement as annotation that is orthogonal to `@nospecialize` After trying 1 ~ 3., I decided to submit 3. for now, because I think the interface is ready to be experimented. ### 1. make `@nospecialize` avoid inference also This is almost same as what Jameson has done at <vtjnash@8ab7b6b>. It turned out that this approach performs very badly because some of `@nospecialize`'d arguments still need inference to perform reasonably. For example, it's obvious that the following definition of `getindex(@nospecialize(t::Tuple), i::Int)` would perform very badly if `@nospecialize` blocks inference, because of a lack of useful type information for succeeding optimizations: <https://github.com/JuliaLang/julia/blob/12d364e8249a07097a233ce7ea2886002459cc50/base/tuple.jl#L29-L30> ### 2. add noinfer effect when `@nospecialize`d method is annotated as `@noinline` also The important observation is that we often use `@nospecialize` even when we expect inference to forward type and constant information. Adversely, we may be able to exploit the fact that we usually don't expect inference to forward information to a callee when we annotate it as `@noinline`. So the idea is to enable the inference suppression when `@nospecialize`'d method is annotated as `@noinline` also. It's a reasonable choice, and could be implemented efficiently after <#41922>. But it sounds a bit weird to me to associate no infer effect with `@noinline`, and I also think there may be some cases we want to inline a method while _partially_ avoiding inference, e.g.: ```julia # the compiler will always infer with `f::Any` @noinline function twof(@nospecialize(f), n) # we really want not to inline this method body ? if occursin('+', string(typeof(f).name.name::Symbol)) 2 + n elseif occursin('*', string(typeof(f).name.name::Symbol)) 2n else zero(n) end end ``` ### 3. implement as `@pure`-like boolean annotation to request noinfer effect on top of `@nospecialize` So this is what this commit implements. It basically replaces the previous `@noinline` flag with newly-introduced annotation named `@noinfer`. It's still associated with `@nospecialize` and it only has effect when used together with `@nospecialize`, but now it's not associated to `@noinline` at least, and it would help us reason about the behavior of `@noinfer` and experiment its effect more reliably: ```julia # the compiler will always infer with `f::Any` Base.@noinfer function twof(@nospecialize(f), n) # the compiler may or not inline this method if occursin('+', string(typeof(f).name.name::Symbol)) 2 + n elseif occursin('*', string(typeof(f).name.name::Symbol)) 2n else zero(n) end end ``` ### 4. implement as annotation that is orthogonal to `@nospecialize` Actually, we can have `@nospecialize` and `@noinfer` separately, and it would allow us to configure compilation strategies in a more fine-grained way. ```julia function noinfspec(Base.@noinfer(f), @nospecialize(g)) ... end ``` I'm fine with this approach, if initial experiments show `@noinfer` is useful.
- Loading branch information
Showing
15 changed files
with
204 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.