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
predicatefor parameter types - Disable during development using Preferences.jl?
- Support for
UnionAlltypes? - Specialization hints?
Conclusion
Please reach out with comments, questions, suggestions, issues, and contributions.
Thank you for taking the time to checkout Speculator.jl!
