Introduction
Hi everyone! I’m excited to announce Speculator.jl, which reduces latency by automatically searching for compilable method specializations.
Features
The primary feature is the function speculate
. It will search the methods
of a callable value and recursively search the values of a Module
. Some specializations are known to already be compiled, and are skipped. The verbosity
can be used to specify various logging behavior,
which only shows warnings for failed calls to precompile
by default.
julia> speculate(Int; verbosity = debug)
[ Info: Skipped `Int64(::Float64)`
[ Info: Compiled `Int64(::Float32)`
[ Info: Compiled `Int64(::Float16)`
Values obtained from a module can also be skipped by providing a predicate
as the first parameter.
julia> module Example
export f
f() = nothing
g(::Int) = nothing
end;
julia> speculate(Base.ispublic, Example; verbosity = debug)
[ Info: Compiled `Main.Example.f()`
julia> speculate(Example; verbosity = debug)
[ Info: Skipped `Main.Example.f()`
[ Info: Compiled `Main.Example.g(::Int64)`
By default, speculate
will attempt to compile each specialization. Instead, they can be skipped by setting dry = true
, which is useful for testing which specializations are generated.
julia> speculate(Char; dry = true, verbosity = debug)
[ Info: Skipped `Char(::LinearAlgebra.WrapperChar)`
[ Info: Skipped `Char(::UInt32)`
Methods with abstractly typed parameters are also searched. The search generates specializations using the Cartesian product of each parameter’s subtypes. Since the number of specializations is exponentially large, the search skips methods whose number of specializations exceeds the limit
. The default limit
is 1
, which will generate every specialization where each of the method’s parameters are either a concrete type or have been annotated with @nospecialize
.
julia> i(::Union{String, Symbol}, ::AbstractChar) = nothing;
julia> speculate(i; limit = 4, verbosity = debug)
[ Info: Compiled `Main.i(::Symbol, ::LinearAlgebra.WrapperChar)`
[ Info: Compiled `Main.i(::String, ::LinearAlgebra.WrapperChar)`
[ Info: Compiled `Main.i(::Symbol, ::Char)`
[ Info: Compiled `Main.i(::String, ::Char)`
Precompilation directives may also be saved by specifying a path
.
julia> path = tempname();
julia> speculate(Bool; path)
julia> print(read(path, String))
precompile(Tuple{Type{Bool}, BigFloat})
precompile(Tuple{Type{Bool}, Float16})
The value all_modules
can be used to search every loaded module. Since the limit
is 1
, this example finds 6354
methods with only a single possible specialization out of 26786
total methods.
julia> speculate(all_modules; dry = true, verbosity = review)
[ Info: Generated `6354` methods from `26786` generic methods in `2.4881` seconds
Package Precompilation
Calls to speculate
are designed for use in a package to automatically handle precompilation.
The function is suitable to call from the top-level, because it only runs when called during package precompilation, to save precompilation directives to a path
, and during an interactive session. In fact, the precompilation for Speculator.jl itself is simply speculate(Speculator; limit = 4)
. A call to speculate(Base.ispublic, ::Module)
at the end of a package ensures that every concretely typed method of public values is precompiled.
Interactive Use
By default, speculate
will run in the foreground. It can instead be ran in another thread by setting background = true
. This is useful in an interactive session, because compilation can occur while code is not being ran.
julia> using Plots
julia> speculate(Plots; background = true, verbosity = review)
julia> plot(1)
┌ Info: Generated `554` methods from `2114` generic methods in `11.1532` seconds
│ Compiled `313`
│ Skipped `241`
└ Warned `0`
Use install_speculator
to automatically call speculate
, with background = true
by default, on the input value. This can also be placed in a startup.jl
file and be removed using uninstall_speculator
.
julia> install_speculator(; verbosity = review)
[ Info: The input speculator has been installed into the REPL
julia> f() = nothing;
┌ Info: Generated `1` methods from `1` generic methods in `0.0148` seconds
│ Compiled `1`
│ Skipped `0`
└ Warned `0`
julia> Iterators
Base.Iterators
┌ Info: Generated `25` methods from `180` generic methods in `0.6180` seconds
│ Compiled `22`
│ Skipped `3`
└ Warned `0`
Comparisons
This is a brief overview of precompilation in similar packages. More detailed comparisons to other techniques will be documented in a future release. Speculator.jl is unique among these techniques in that it supports automatic speculative compilation in the REPL.
PrecompileTools.jl
This package automates precompilation by running a workload. Additionally, it supports healing invalidations and precompiling methods from dynamic dispatch. However, it requires maintaining and running a workload. The workload retains the benefit of ensuring that it is precompiled, whereas Speculator.jl may be able to precompile all, some, or none of the workload.
PrecompileSignatures.jl
This package implements similar functionality as Speculator.jl; notably that PrecompileSignatures.@precompile_signatures ::Module
is roughly equivalent to Speculator.speculate(::Module)
. However, it only handles Union
parameter types and has less configuration.
Future Work
- More documenation
- Check the
predicate
for parameter types - Disable during development using Preferences.jl?
- Support for
UnionAll
types? - Specialization hints?
Conclusion
Please reach out with comments, questions, suggestions, issues, and contributions.
Thank you for taking the time to checkout Speculator.jl!