PrecompileTools.@compile_workload does not seem to work at all - what am I doing wrong?

Hi there, I am testing PrecompileTools, and I just don’t get it to work - despite precompilation seems to work (check by MethodAnalysis.methodinstances), julia just recompiles again and again und process restart…

module testprecompile

import PrecompileTools
# EDIT: added @recompile_invalidations
PrecompileTools.@recompile_invalidations using Plots

function main()
    plot(rand(10))
    savefig("julia_output.png")
end

PrecompileTools.@compile_workload main()
end

Put this into its own package testprecompile, add the dependencies and take a look whether precompilation worked. E.g. using @time

julia> import testprecompile  # I already precompiled it beforehand
testprecompile
julia> @time testprecompile.main()  # shows that compilation is just done again
  2.470182 seconds (39.70 k allocations: 2.208 MiB, 68.15% compilation time)
"/home/user/Tmp/tmp-julia/testprecompile/julia_output.png"

Apparently it does not work.

Also if I check it via command line --trace-compile=mytrace.jl, I also see all the precompile statements, despite they should already be precompiled.

How can I get this to work so that precompilation is actually effective?

running Julia 1.9.4

The reason it “doesn’t work” is because it does work: the Plots developers have presumably already precompiled everything you’re asking for: Code search results · GitHub. Doing it again doesn’t change anything. Though I’m surprised how long your measured time is, for me on Julia 1.10 on an unremarkable laptop it’s

tim@diva:/tmp/TestPrecompile$ julia --project --startup=no -q
julia> using TestPrecompile

julia> @time TestPrecompile.main()
  0.178089 seconds (14.50 k allocations: 999.664 KiB, 83.28% compilation time)
"/tmp/TestPrecompile/julia_output.png"

which is about 20x faster than you’re reporting.

How much recompilation you see depends on invalidations, and that means it depends entirely on what other packages you loaded into your environment before loading testprecompile. Do you have a lot of stuff in your startup.jl? Try running Julia with --startup=no.

There is some residual stuff that just won’t compile, however. This seems to be because some Plots code invalidates Base methods that Plots itself uses. I would say that for times down to 0.1s there is possibly more work to be done, but this is so much better than it used to be that there hasn’t been that much pressure to dig into it.

4 Likes

My system is apparently a bit slow at the moment… I need to investigate this further definitely

I now added @recompile_invalidations and --startup=no, but I am still seeing significant amounts of compilation happening.

julia --project --startup=no                                                                  
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.9.4 (2023-11-14)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> import testprecompile

julia> @time testprecompile.main()
  0.498227 seconds (39.52 k allocations: 2.189 MiB, 65.09% compilation time)

julia> @time testprecompile.main()
  0.026232 seconds (4.53 k allocations: 355.812 KiB)

A factor of 10 which is not captured by precompilation.

Of course, I am probably super glad to have the amount of precompilation which is already there, but it surprises me a lot that Julia is not able to store and reuse all compilations of a concrete runnable example in such significant ways.

I still have the feeling that there could be an easy fix to make precompilation work almost completely in these cases. It seems like julia is just ignoring previous precompilations for some reasons…

1 Like

There probably is something to that. If you run Julia with --trace-compile you’ll see that everything compiled is stuff like this:

...
precompile(Tuple{typeof(Base.float), Int64})
precompile(Tuple{typeof(Base.:(*)), Float64, Float64})
precompile(Tuple{typeof(Base.max), Float64, Float64})
precompile(Tuple{typeof(Base.ceil), Type{Int64}, Float64})
precompile(Tuple{typeof(Base.:(+)), Int64, Bool})
precompile(Tuple{typeof(Base.occursin), String, String})
precompile(Tuple{typeof(Base.:(>)), Float64, Float64})
precompile(Tuple{typeof(Base.max), Int64, Float64})
precompile(Tuple{typeof(Base.floor), Float64})
precompile(Tuple{typeof(Base.iseven), Int64})
precompile(Tuple{typeof(Base.isempty), Base.OneTo{Int64}})
precompile(Tuple{typeof(Base._array_for), Type{Int64}, Base.HasShape{1}, Tuple{Base.OneTo{Int64}}})
precompile(Tuple{Type{Base.UnitRange{T} where T<:Real}, Int64, Int64})
precompile(Tuple{typeof(Base.getindex), Base.OneTo{Int64}, Base.UnitRange{Int64}})
precompile(Tuple{typeof(Base.firstindex), Array{Float64, 1}})
precompile(Tuple{typeof(Base.getindex), Base.OneTo{Int64}, Int64})
precompile(Tuple{typeof(Base.:(<)), Int64, Float64})
precompile(Tuple{typeof(Base.:(>)), Int64, Float64})
precompile(Tuple{typeof(Base.:(+)), Int64, Float64})
precompile(Tuple{typeof(Base.setindex!), Array{Float64, 1}, Float64, Int64})
...

It seems like these must be from invalidations, but the repair from @recompile_invalidations is clearly incomplete. Now I’m a bit puzzled about why I made it only recompile the leaves, it seems like it should recompile everything that got invalidated.(nvm, it makes sense: recompiling the leaves will recompile all their dependencies.) But a lot of these items don’t even show up in the list of invalidated methods, which I find confusing.

See (Re)compilation without invalidation · Issue #50720 · JuliaLang/julia · GitHub

2 Likes

Thank you a lot. I am looking forward to the next generation of PrecompileTools :slight_smile: