-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider generate_precompile
prior to module close
#38951
Comments
What does this mean concretely? Is it enough to put them before the close of |
I'm not sure I understand why moving the precompile statements changes anything. Any insights? |
I don't have the complete picture, but there have long been certain methods that just don't "take" when you try to precompile them. The phenotype is that you can add a precompile statement, and then the next Here are some PRs that deal with the issue in a particularly focused way:
And here's a demo (you need MethodAnalysis and SnoopCompile installed): # This script will create a new package, PrecompileTest, in your temporary directory
using Pkg, MethodAnalysis, SnoopCompile
pkgdir = joinpath(tempdir(), "PrecompileTest")
Pkg.generate(pkgdir)
open(joinpath(pkgdir, "src", "PrecompileTest.jl"), "w") do io
write(io, """
module PrecompileTest
function precompile_me()
# Pick a Dict type that Julia hasn't precompiled
dict = Dict{Cmd,Float16}()
key = `ls -a`
dict[key] = 2
return dict, sin(dict[key])
end
and_me() = exp(Float16(2))
if ccall(:jl_generating_output, Cint, ()) == 1
@assert precompile(precompile_me, ())
@assert precompile(and_me, ())
end
end
"""
)
end
# First, show that a regular Julia session lacks MethodInstances
@info "The following 3 lines should be `nothing`"
display(methodinstance(sin, (Float16,)))
display(methodinstance(exp, (Float16,)))
display(methodinstance(setindex!, (Dict{Cmd,Float16}, Float16, Cmd)))
push!(LOAD_PATH, dirname(pkgdir))
using PrecompileTest
# Check for backedges
@info "The next few lines should have MethodInstance content"
mi = methodinstance(sin, (Float16,))
display(mi)
display(mi.backedges) # all is good with the world!
mi = methodinstance(exp, (Float16,))
display(mi)
isa(mi, Core.MethodInstance) && display(mi.backedges) # the `isa` is to facilitate commenting out, see below
@warn "The next should too, but it doesn't"
mi = methodinstance(setindex!, (Dict{Cmd,Float16}, Float16, Cmd))
display(mi) # uh oh
# Now run it in this session
@warn "Running `precompile_me` while snooping on inference --- this is bad because `setindex!` is really expensive to infer; it also seems to mess up others"
tinf = @snoopi PrecompileTest.precompile_me()
display(tinf)
@info "Running `and_me` while snooping on inference---all's great here!"
tinf = @snoopi PrecompileTest.and_me()
display(tinf)
@info "Try commenting out the `precompile(and_me, ())` line, you'll see it worked only because we'd precompiled it" Here is the output: julia> include("pctest.jl")
Generating project PrecompileTest:
/tmp/PrecompileTest/Project.toml
/tmp/PrecompileTest/src/PrecompileTest.jl
[ Info: The following 3 lines should be `nothing`
nothing
nothing
nothing
[ Info: Precompiling PrecompileTest [4a68bf2e-391a-4167-b5ef-fb7a905be883]
[ Info: The next few lines should have MethodInstance content
MethodInstance for sin(::Float16)
1-element Vector{Any}:
MethodInstance for PrecompileTest.precompile_me()
MethodInstance for exp(::Float16)
1-element Vector{Any}:
MethodInstance for PrecompileTest.and_me()
┌ Warning: The next should too, but it doesn't
└ @ Main ~/.julia/dev/pctest.jl:45
nothing
┌ Warning: Running `precompile_me` while snooping on inference --- this is bad because `setindex!` is really expensive to infer; it also seems to mess up others
└ @ Main ~/.julia/dev/pctest.jl:50
4-element Vector{Tuple{Float64, Core.MethodInstance}}:
(0.0008871555328369141, MethodInstance for Base.ht_keyindex(::Dict{Cmd, Float16}, ::Cmd))
(0.0017960071563720703, MethodInstance for Dict{Cmd, Float16}())
(0.02426600456237793, MethodInstance for setindex!(::Dict{Cmd, Float16}, ::Int64, ::Cmd))
(0.03497600555419922, MethodInstance for sin(::Float32))
[ Info: Running `and_me` while snooping on inference---all's great here!
Tuple{Float64, Core.MethodInstance}[]
[ Info: Try commenting out the `precompile(and_me, ())` line, you'll see it worked only because we'd precompiled it
For reference, here's currently what Revise has to infer (this is running on #38906, so those particular methods are now gone). I'm doing this demo from julia> Revise # check that it's not loaded
ERROR: UndefVarError: Revise not defined
julia> using SnoopCompile
julia> @snoopi using Revise
2-element Vector{Tuple{Float64, Core.MethodInstance}}:
(9.512901306152344e-5, MethodInstance for JuliaInterpreter.__init__())
(0.01444697380065918, MethodInstance for setindex!(::Dict{Base.PkgId, Revise.PkgData}, ::Revise.PkgData, ::Base.PkgId))
julia> @snoopi begin
# I've created a file, Example1.jl, with a small change to Example.jl
cp("Example1.jl", "Example.jl"; force=true)
sleep(0.01) # for FileWatching
revise()
end
5-element Vector{Tuple{Float64, Core.MethodInstance}}:
(0.00021195411682128906, MethodInstance for Revise.revise())
(0.00179290771484375, MethodInstance for Dict{Tuple{Revise.PkgData, String}, Nothing}())
(0.0025949478149414062, MethodInstance for sort!(::Vector{Tuple{Revise.PkgData, String}}, ::Int64, ::Int64, ::Base.Sort.MergeSortAlg, ::Base.Order.Lt{Base.Order.var"#1#3"{typeof(Revise.pkgfileless), typeof(identity)}}, ::Vector{Tuple{Revise.PkgData, String}}))
(0.004344940185546875, MethodInstance for empty!(::Dict{Tuple{Revise.PkgData, String}, Nothing}))
(0.008261919021606445, MethodInstance for copyto!(::Vector{Tuple{Revise.PkgData, String}}, ::Set{Tuple{Revise.PkgData, String}})) That's pretty good, but it's like that only because of #38906 and because I and others have ensured that Revise and its whole stack are very well inferrable (check PRs to JuliaInterpreter, LoweredCodeUtils, and Revise over the last six months) to make it eminently precompilable---virtually all of the latency is codegen, as you can see from the fairly small numbers above. By contrast, a package like Makie is definitely not well precompilable, see MakieOrg/Makie.jl#792, and inference accounts for the majority of its TTFP. The flamegraphs there are not profile data but inference-time data. |
Not sure, but my guess is that for |
@timholy - Do you think this phenomenon of method instances that don't serialize their precompilation results is the same thing that I observed, here?: |
I suspect it's the same. |
Still worth considering? |
It's possible we should consider changing the strategy for the
gen_precompile
strategy; as noticed in #38906, not all statements are effectively precompilable. Here's a demo. First, if youprecompile
something that's well-and-truly compiled, it looks like this:Now let's try another method:
I'll show the time to
precompile
in a minute, but inspired by #32705 let's first peek at the method'sroots
:Definitely not truly precompiled. And sure enough:
The
roots
table expanded after compiling it, due to inlining its callees.A way to solve this would to be to have our precompile generator emit statements before the final
end
of the module definition. That's a pretty big change in how we do this, of course; it would be worth discussing first whether #32705 can be fixed.The text was updated successfully, but these errors were encountered: