Skip to content
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

FUSE and Julia 1.9 #252

Closed
orso82 opened this issue Mar 14, 2023 · 8 comments
Closed

FUSE and Julia 1.9 #252

orso82 opened this issue Mar 14, 2023 · 8 comments
Labels

Comments

@orso82
Copy link
Member

orso82 commented Mar 14, 2023

I have tried 1.9 (since this issue appears now to be solved JuliaLang/julia#48473) and things seems to import faster (10s Vs 40s). That said, 1.9 seems to be a slower at compiling (420 first run, 60 later runs) Vs (230s first run, 55s later runs) of 1.8.

@sjkelly may ask you to investigate this further?

@orso82
Copy link
Member Author

orso82 commented Mar 15, 2023

To take advantage of 1.9 we MUST use snoop-compile and pre-compilation statements.

Why 1.9 takes longer than 1.8? 1.9 is paying pre-compilation time upfront.

@sjkelly need to use pre-compilation packages.

@sjkelly Possible solution: disable the pre-compilation for development purposes.

@sjkelly
Copy link

sjkelly commented Mar 15, 2023

I will try to give a few definitions and explanations that may help explain why the timing numbers seem to be varying. There are a few main improvements in 1.9 that influence all three stages of precompilation, loading, and first-run-latency.

Precompilation: This is the time it takes for Pkg to do Pkg.precompile() for the current package. In Julia 1.0-1.7 this would cache a serialized and partially evaluated version of the code. In 1.8 this added the ability to store partial type inference results. In 1.9 this now has the added ability to cache native machine code (just like an .so in C), through a "pkgimage". The julia will try to cache all methods signatures specified by a call to precompile.

Loading: This is the time it takes to do e.g. using MyPkg. This will include the time taken to load code form disk to memory, and for the Julia runtime to insert new methods into the method tables. In Julia 1.9 the ability to process weakdeps was added. Traditionally Requires.jl was used for such tasks, but weakdeps integrates closer to code loading, to allow for conditional code loading based on the dependency structure of a given project. This greatly reduces the code loading time on many projects. For example: Plots will only load code for displaying units if Unitful is also loaded in the project.

First-run-latency: This is the time taken for the first call to e.g. MyPkg.my_function(). In versions prior to 1.9 this would trigger a Just-in-time (JIT) compilation. With 1.9, the function call can be preemptively compiled during the precompilation stage, eliminating such JIT latency as the code is now entirely stored in the pkgimage.

There are some tools to make generating precompile statements easier, most notably SnoopPrecompile.jl.
You can use SnoopPrecompile.jl in FUSE like so:

@precompile_setup begin
    @precompile_all_calls begin
        FUSE.warmup()
    end
end

Now, of course on Julia 1.9 the precompilation stage will be doing more work to cache the native code. As discussed, there are a few workflows you can explore.

Default:
You drop all the statements inside @precompile_all_call and you pay a big precompilation cost, and pay a mild loading time penalty. But: all subsequent calls that are inside @precompile_all_calls will be fast. This is great for deployments and production settings.

No Pkgimages:
You can opt out of the new pkg image functionality by starting julia with the --pkgimages=no flag.

Selective filtering:
You can use a LocalPreferences.toml to disable precomilation in any package that uses SnoopPrecompile. You can generate the LocalPreferences.toml file to skip precompilation for all packages with the following script:

using Preferences
using SnoopPrecompile

pkg_names = map(p -> p.name, values(Pkg.dependencies()))
@info "Environment includes $(length(pkg_names)) packages"

@info "Minimizing precompilation traces with `skip_precompile`"
delete_preferences!(SnoopPrecompile, "skip_precompile", force=true)
set_preferences!(SnoopPrecompile, "skip_precompile" => pkg_names)

@orso82
Copy link
Member Author

orso82 commented Mar 15, 2023

Thank you @sjkelly ! This is very helpful! Have you tried to use SnoopPrecompile.jl on FUSE, and how do the timing change?

Can you comment about the default approach plays with Revise.jl?

sjkelly added a commit that referenced this issue Mar 15, 2023
This is a simple test of the native code-caching functionality
in Julia 1.9. This should greatly improve runtime, at the
expense of greater precompilation time and slightly
greater package load time:

With this PR, the runtime on Julia 1.9:

```
julia> @time using FUSE
 21.957530 seconds (53.10 M allocations: 3.644 GiB, 8.49% gc time, 7.02% compilation time: 13% of which was recompilation)

julia> @time FUSE.warmup()
 43.951729 seconds (501.41 M allocations: 29.219 GiB, 8.02% gc time, 6.47% compilation time)
```

Whereas master:

```
julia> @time using FUSE
 15.015840 seconds (42.16 M allocations: 2.971 GiB, 8.52% gc time, 10.46% compilation time: 13% of which was recompilation)

julia> @time FUSE.warmup()
 373.581675 seconds (796.96 M allocations: 46.873 GiB, 5.31% gc time, 87.77% compilation time: <1% of which was recompilation)
```

xref: #252
xref: #218
@sjkelly
Copy link

sjkelly commented Mar 15, 2023

There is a sample timing here: #259

The Revise workflows should not be affected at all, and it will work as it normally does.

@orso82
Copy link
Member Author

orso82 commented Mar 15, 2023

Ok, I think I am starting to get a sense for how this works.

Precompilation is nice BUT to really take advantage of it one must absolutely not change anything in the codebase. Once things get pre-compiled, any changes to the source will trigger a whole new pre-compilation (this time burdened by our warmup, which will make precompile very slow). I now understand why you were saying this is fantastic for deployments and production settings, but not really too useful for development.

I can see that for development purposes we may want to precompile everything that is not under ProjectTorreyPines. I'll give it a try.

Question for @sjkelly and @ChrisRackauckas: Is there a way to generate a pkgimage from a running Julia session? If so, one would be able to hack the code (as usual, using Revise.jl), and then generate a pkgimage when needing to start a new Julia session. The new Julia session would not see a difference between the pkgimage and the source, thus not triggering another precompilation. Makes sense?

@jguterl
Copy link
Contributor

jguterl commented Mar 16, 2023 via email

@sjkelly
Copy link

sjkelly commented Mar 16, 2023

Precompilation is nice BUT to really take advantage of it one must absolutely not change anything in the codebase. Once things get pre-compiled, any changes to the source will trigger a whole new pre-compilation (this time burdened by our warmup, which will make precompile very slow). I now understand why you were saying this is fantastic for deployments and production settings, but not really too useful for development.

Yes, this is correct. So in this case you would want maybe a LocalPreferences.toml with the following:

[SnoopPrecompile]
skip_precompile = ["CHEASE", "CoordinateConventions", "EPEDNN", "FUSE", "FiniteElementHermite", "Fortran90Namelists", "FusionMaterials", "IMAS", "IMASDD", "MXHEquilibrium", "MeshTools", "MillerExtendedHarmonic", "NNeutronics", "QED", "SimulationParameters", "TAUENN", "TEQUILA", "TGLFNN", "VacuumFields"]

So all the GA repos will opt out of precompilation, but you still retain the benefits in dependencies.

Question for @sjkelly and @ChrisRackauckas: Is there a way to generate a pkgimage from a running Julia session? If so, one would be able to hack the code (as usual, using Revise.jl), and then generate a pkgimage when needing to start a new Julia session. The new Julia session would not see a difference between the pkgimage and the source, thus not triggering another precompilation. Makes sense?

This is currently not possible, though I imagine this would be something great to have in the long term. I will bring this idea up with the compiler folks and see what they have to say.

@orso82
Copy link
Member Author

orso82 commented Mar 21, 2023

SnoopPrecompile in FUSE is handled via the WarmupFUSE.jl package

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants