[ANN] PrecompileSignatures.jl: Generate precompile directives by reading method signatures

PrecompileSignatures.jl is a package which reduces TTFX by moving some just in time compilation to the precompilation phase. The package does this by reading all method signatures in a package and generating precompile directives for any concrete signature that it can find.

This idea of this package came when reducing the TTFX for Pluto.jl. At one point we realized that adding one precompile directive reduced the TTFX for one of Pluto’s main methods by 3 seconds (see Use `precompile` for `SessionActions.open` by rikhuijzer · Pull Request #1934 · fonsp/Pluto.jl · GitHub for details). Once we realized that, we started to manually add precompile directives. This worked quite well, but is hard to maintain. With PrecompileSignatures.jl, adding the directives is now automated.

How much this package can reduce the TTFX depends on the package. The more signatures a package has with concretely typed arguments, the more precompile directives can be added. Next, the better the types inside the methods can be infered, the more performance can be gained from adding the directives. This last point depends a lot on Julia’s compiler. Newer Julia versions have a better compiler and better caching which make precompile more effective.

In general, I expect that the packages which are applications such as Pluto.jl or Genie.jl or where methods cannot be easily executed during precompilation due to side-effects will benefit the most from PrecompileSignatures.jl. Note that the precompile directives are generated during the precompilation phase, so there is no need to store separate src/precompile.jl files in your repository.

For more info, see the README.

25 Likes

Just one thing, if I understood well the README the precompilation works for methods whose arguments have been annotated with their type and these are concrete… this isn’t however in contrast to trying to keep the function signatures the more general as possible in order to facilitate code composability ?

3 Likes

As I recall it used to be possible to generate precompile statements from the package’s tests using SnoopCompile.jl
I am pretty sure it still is.
But IIRC it is no longer highlighted as a main use case as there was a reason to avoid adding huge numbers of precompile statements without some thought.
Though I can’t remember what it was. @tim.holy ?

2 Likes

It is easier to just have a small representative workload that runs during precompile time. That will always be updated, even if function names etc change in the package.

5 Likes

Depends on the package. For a package like Pluto there is almost zero need to keep things generic. Especially at the more high-level functions.

4 Likes

Agreed. Or PrecompileSignatures which generates the precompile directives during the precompilation phase. The drawback of a workload is that it can also become outdated and can be problematic when there are side-effects.

PackageCompile, SnoopCompile, workload and PrecompileSignatures all have their drawbacks and benefits IMO.

1 Like

I’m also working on automatically converting some abstract types to concrete. It currently automatically converts AbstractString to String before generating precompile directives. Often, methods precompiled for some subtype of an abstract type are also quicker for other subtypes, so that is why doing this is useful.

I’m still working on making this API public to pass the types and experiment with it. For example, for a package like Distributions, we could precompile all Reals as Float64 since floats are passed into the methods 90% of the time.

1 Like