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

account for extensions indirectly depending on each other in parallel package precompilation #53972

Merged
merged 1 commit into from
May 9, 2024

Conversation

KristofferC
Copy link
Member

In the parallel package precompilation code we are mapping what packages depend on what other packages so that we precompile things in the correct order ("bottom up") and so what we can also detect cycles and avoid precompiling packages in those cycles. However, we fail to detect one type of dependency which is that an extension can "indirectly" depend on another extension. This happens when the transitive dependencies of the extension (it's parent + triggers) are a superset of the dependencies of another extension. In other words, this means that the other extension will get loaded into the first extension once it gets loaded, effectively being a dependency.

The failure to model this leads to some issues, for example using one of the examples in our own tests:

julia> Base.active_project()
"/home/kc/julia/test/project/Extensions/HasDepWithExtensions.jl/Project.toml"

(HasDepWithExtensions) pkg> status --extensions
Project HasDepWithExtensions v0.1.0
Status `~/julia/test/project/Extensions/HasDepWithExtensions.jl/Project.toml`
  [4d3288b3] HasExtensions v0.1.0 `../HasExtensions.jl`
              ├─ ExtensionFolder [ExtDep, ExtDep2]
              ├─ Extension [ExtDep]
              └─ LinearAlgebraExt [LinearAlgebra]

julia> Base.Precompilation.precompilepkgs(; timing=true)
Precompiling all packages...
    196.1 ms  ✓ HasExtensions
    244.4 ms  ✓ ExtDep2
    207.9 ms  ✓ SomePackage
    201.6 ms  ✓ ExtDep
    462.5 ms  ✓ HasExtensions  ExtensionFolder
    200.1 ms  ✓ HasExtensions  Extension
    173.1 ms  ✓ HasExtensions  LinearAlgebraExt
    222.2 ms  ✓ HasDepWithExtensions
  8 dependencies successfully precompiled in 2 seconds

julia> Base.Precompilation.precompilepkgs(; timing=true)
Precompiling all packages...
    213.4 ms  ✓ HasExtensions  ExtensionFolder
  1 dependency successfully precompiled in 0 seconds. 7 already precompiled.

julia> Base.Precompilation.precompilepkgs(; timing=true)

julia>

We can see here that ExtensionFolder gets precompiled twice which is due to Extension actually being an "indirect dependency" of ExtensionFolder and therefore should be precompiled before it.

With this PR we instead get:

julia> Precompilation.precompilepkgs(; timing=true)
Precompiling all packages...
    347.5 ms  ✓ ExtDep2
    294.0 ms  ✓ SomePackage
    293.2 ms  ✓ HasExtensions
    216.5 ms  ✓ HasExtensions  LinearAlgebraExt
    554.9 ms  ✓ ExtDep
    580.9 ms  ✓ HasExtensions  Extension
    593.8 ms  ✓ HasExtensions  ExtensionFolder
    261.3 ms  ✓ HasDepWithExtensions
  8 dependencies successfully precompiled in 2 seconds

julia> Precompilation.precompilepkgs(; timing=true)

julia> 

Extension is precompiled after ExtensionFolder and nothing happens on the second call.

Also, with this PR we get for the issue in #53081 (comment):

(jl_zuuRGt) pkg> st
Status `/private/var/folders/tp/2p4x9ygx48sgsdl1ccg1mp_40000gn/T/jl_zuuRGt/Project.toml`
⌃ [d236fae5] PreallocationTools v0.4.17
⌃ [0c5d862f] Symbolics v5.16.1
Info Packages marked with ⌃ have new versions available and may be upgradable.

julia> Precompilation.precompilepkgs(; timing=true)
┌ Warning: Circular dependency detected. Precompilation will be skipped for:
│   SymbolicsPreallocationToolsExt
│   SymbolicsForwardDiffExt

and we avoid precompiling the problematic extensions.

This should also allow extensions to precompile in parallel which I think was prevented before (from the code that is removed in the beginning of the diff).

WIP: Because I need to think about this more and test it more etc.

@KristofferC
Copy link
Member Author

This seems to be generally more correct since it fixes other issues that pop up due to the "fake dependencies" that extensions now have on each other JuliaLang/Pkg.jl#3889. I'll run a PkgEval to evaluate this a bit more though.

@KristofferC
Copy link
Member Author

@nanosoldier runtests()

@KristofferC KristofferC changed the title WIP account for extensions indirectly depending on each other in parallel package precompilation account for extensions indirectly depending on each other in parallel package precompilation May 8, 2024
Copy link
Member

@IanButterworth IanButterworth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow missed this until now. SGTM

@nanosoldier
Copy link
Collaborator

The package evaluation job you requested has completed - possible new issues were detected.
The full report is available.

@KristofferC
Copy link
Member Author

Looking at the PkgEval log, this PR detects circular deps in:

 "BEASTDataPrep"
 "BayesianOptimization"
 "CLEARSWI"
 "ClimaUtilities"
 "DataManipulation"
 "DictArrays"
 "EconomicScenarioGenerators"
 "EfficientGlobalOptimization"
 "FinanceModels"
 "FixedWidthTables"
 "InterferometricModels"
 "JutulDarcy"
 "KrigingModel"
 "Microstructure"
 "ModiaPlot_CairoMakie"
 "ModiaPlot_WGLMakie"
 "MonteCarloTesting"
 "ParallelAnalysis"
 "Pingouin"
 "PolyaGammaDistribution"
 "QuasiCopula"
 "ROMEO"
 "ScikitLearn"
 "SimSpin"
 "StanModels"

If we look at the log for a random one:

https://s3.amazonaws.com/julialang-reports/nanosoldier/pkgeval/by_hash/f0f464b_vs_88d6633/ScikitLearn.primary.log:

Precompiling ScikitLearn dependencies...
┌ Warning: Circular dependency detected. Precompilation will be skipped for:
│   Base.PkgId(UUID("cc18c42c-b769-54ff-9e2a-b28141a64aae"), "GaussianMixtures")
│   Base.PkgId(UUID("ffbe0ea5-a612-5ff7-aaf5-cac02eef3019"), "DistributionsTestExt")
│   Base.PkgId(UUID("891a1506-143c-57d2-908e-e1f8e92e6de9"), "GaussianProcesses")
│   Base.PkgId(UUID("6db1f127-056a-568b-bd49-ae61d42389fa"), "DistributionsChainRulesCoreExt")
└ @ Base.Precompilation precompilation.jl:555
Precompiling all packages...

https://s3.amazonaws.com/julialang-reports/nanosoldier/pkgeval/by_hash/f0f464b_vs_88d6633/ScikitLearn.against.log

┌ Distributions → DistributionsTestExt
│  ┌ Warning: Module DistributionsTestExt with build ID ffffffff-ffff-ffff-0006-3a096a8d2697 is missing from the cache.
│  │ This may mean DistributionsTestExt [ffbe0ea5-a612-5ff7-aaf5-cac02eef3019] does not support precompilation but is imported by a module that does.
│  └ @ Base loading.jl:2409
│  ┌ Error: Error during loading of extension DistributionsTestExt of Distributions, use `Base.retry_load_extensions()` to retry.
│  │   exception =
│  │    1-element ExceptionStack:
│  │    Error when precompiling module, potentially caused by a __precompile__(false) declaration in the module.
│  │    Stacktrace:
│  │      [1] _require(pkg::Base.PkgId, env::Nothing)
│  │        @ Base ./loading.jl:2413
│  │      [2] __require_prelocked(uuidkey::Base.PkgId, env::Nothing)
│  │        @ Base ./loading.jl:2266

@KristofferC
Copy link
Member Author

@nanosoldier runtests(["MLJTSVDInterface", "MbedTLS", "Flamenco", "DynamicPolynomials", "FMIImport", "GaussianKDEs", "Permanents", "OptimizationPRIMA", "Miter", "SSIMLoss", "DECAES", "TransitionsInTimeseries", "MPIMeasurements", "SphericalFunctions", "TidierFiles", "SciMLExpectations", "ControlSystemIdentification", "PolynomialGTM", "LiquidElectrolytes", "TKTDsimulations", "NumCME", "AtmosphericDeposition", "BloqadeODE"])

@nanosoldier
Copy link
Collaborator

The package evaluation job you requested has completed - no new issues were detected.
The full report is available.

@KristofferC
Copy link
Member Author

I think it is worth trying this on master at least.

@KristofferC KristofferC merged commit df89440 into master May 9, 2024
6 of 8 checks passed
@KristofferC KristofferC deleted the kc/extension_indirect_deps branch May 9, 2024 10:43
@IanButterworth
Copy link
Member

One thing I don't understand is how code loading works if precompilation isn't happy

@KristofferC
Copy link
Member Author

Yeah, I am not sure either.

@fingolfin
Copy link
Member

Should this be backported to at least 1.11?

@KristofferC
Copy link
Member Author

Yeah, that's probably a good idea.

@IanButterworth IanButterworth added the backport 1.11 Change should be backported to release-1.11 label May 25, 2024
KristofferC added a commit that referenced this pull request May 27, 2024
… package precompilation (#53972)

In the parallel package precompilation code we are mapping what packages
depend on what other packages so that we precompile things in the
correct order ("bottom up") and so what we can also detect cycles and
avoid precompiling packages in those cycles. However, we fail to detect
one type of dependency which is that an extension can "indirectly"
depend on another extension. This happens when the transitive
dependencies of the extension (it's parent + triggers) are a superset of
the dependencies of another extension. In other words, this means that
the other extension will get loaded into the first extension once it
gets loaded, effectively being a dependency.

The failure to model this leads to some issues, for example using one of
the examples in our own tests:

```julia
julia> Base.active_project()
"/home/kc/julia/test/project/Extensions/HasDepWithExtensions.jl/Project.toml"

(HasDepWithExtensions) pkg> status --extensions
Project HasDepWithExtensions v0.1.0
Status `~/julia/test/project/Extensions/HasDepWithExtensions.jl/Project.toml`
  [4d3288b3] HasExtensions v0.1.0 `../HasExtensions.jl`
              ├─ ExtensionFolder [ExtDep, ExtDep2]
              ├─ Extension [ExtDep]
              └─ LinearAlgebraExt [LinearAlgebra]

julia> Base.Precompilation.precompilepkgs(; timing=true)
Precompiling all packages...
    196.1 ms  ✓ HasExtensions
    244.4 ms  ✓ ExtDep2
    207.9 ms  ✓ SomePackage
    201.6 ms  ✓ ExtDep
    462.5 ms  ✓ HasExtensions → ExtensionFolder
    200.1 ms  ✓ HasExtensions → Extension
    173.1 ms  ✓ HasExtensions → LinearAlgebraExt
    222.2 ms  ✓ HasDepWithExtensions
  8 dependencies successfully precompiled in 2 seconds

julia> Base.Precompilation.precompilepkgs(; timing=true)
Precompiling all packages...
    213.4 ms  ✓ HasExtensions → ExtensionFolder
  1 dependency successfully precompiled in 0 seconds. 7 already precompiled.

julia> Base.Precompilation.precompilepkgs(; timing=true)

julia>
```

We can see here that `ExtensionFolder` gets precompiled twice which is
due to `Extension` actually being an "indirect dependency" of
`ExtensionFolder` and therefore should be precompiled before it.

With this PR we instead get:

```julia
julia> Precompilation.precompilepkgs(; timing=true)
Precompiling all packages...
    347.5 ms  ✓ ExtDep2
    294.0 ms  ✓ SomePackage
    293.2 ms  ✓ HasExtensions
    216.5 ms  ✓ HasExtensions → LinearAlgebraExt
    554.9 ms  ✓ ExtDep
    580.9 ms  ✓ HasExtensions → Extension
    593.8 ms  ✓ HasExtensions → ExtensionFolder
    261.3 ms  ✓ HasDepWithExtensions
  8 dependencies successfully precompiled in 2 seconds

julia> Precompilation.precompilepkgs(; timing=true)

julia>
```

`Extension` is precompiled after `ExtensionFolder` and nothing happens
on the second call.

Also, with this PR we get for the issue in
#53081 (comment):

```julia
(jl_zuuRGt) pkg> st
Status `/private/var/folders/tp/2p4x9ygx48sgsdl1ccg1mp_40000gn/T/jl_zuuRGt/Project.toml`
⌃ [d236fae5] PreallocationTools v0.4.17
⌃ [0c5d862f] Symbolics v5.16.1
Info Packages marked with ⌃ have new versions available and may be upgradable.

julia> Precompilation.precompilepkgs(; timing=true)
┌ Warning: Circular dependency detected. Precompilation will be skipped for:
│   SymbolicsPreallocationToolsExt
│   SymbolicsForwardDiffExt
```

and we avoid precompiling the problematic extensions.

This should also allow extensions to precompile in parallel which I
think was prevented before (from the code that is removed in the
beginning of the diff).

(cherry picked from commit df89440)
@KristofferC KristofferC removed the backport 1.11 Change should be backported to release-1.11 label May 28, 2024
lazarusA pushed a commit to lazarusA/julia that referenced this pull request Jul 12, 2024
… package precompilation (JuliaLang#53972)

In the parallel package precompilation code we are mapping what packages
depend on what other packages so that we precompile things in the
correct order ("bottom up") and so what we can also detect cycles and
avoid precompiling packages in those cycles. However, we fail to detect
one type of dependency which is that an extension can "indirectly"
depend on another extension. This happens when the transitive
dependencies of the extension (it's parent + triggers) are a superset of
the dependencies of another extension. In other words, this means that
the other extension will get loaded into the first extension once it
gets loaded, effectively being a dependency.

The failure to model this leads to some issues, for example using one of
the examples in our own tests:

```julia
julia> Base.active_project()
"/home/kc/julia/test/project/Extensions/HasDepWithExtensions.jl/Project.toml"

(HasDepWithExtensions) pkg> status --extensions
Project HasDepWithExtensions v0.1.0
Status `~/julia/test/project/Extensions/HasDepWithExtensions.jl/Project.toml`
  [4d3288b3] HasExtensions v0.1.0 `../HasExtensions.jl`
              ├─ ExtensionFolder [ExtDep, ExtDep2]
              ├─ Extension [ExtDep]
              └─ LinearAlgebraExt [LinearAlgebra]

julia> Base.Precompilation.precompilepkgs(; timing=true)
Precompiling all packages...
    196.1 ms  ✓ HasExtensions
    244.4 ms  ✓ ExtDep2
    207.9 ms  ✓ SomePackage
    201.6 ms  ✓ ExtDep
    462.5 ms  ✓ HasExtensions → ExtensionFolder
    200.1 ms  ✓ HasExtensions → Extension
    173.1 ms  ✓ HasExtensions → LinearAlgebraExt
    222.2 ms  ✓ HasDepWithExtensions
  8 dependencies successfully precompiled in 2 seconds

julia> Base.Precompilation.precompilepkgs(; timing=true)
Precompiling all packages...
    213.4 ms  ✓ HasExtensions → ExtensionFolder
  1 dependency successfully precompiled in 0 seconds. 7 already precompiled.

julia> Base.Precompilation.precompilepkgs(; timing=true)

julia>
```

We can see here that `ExtensionFolder` gets precompiled twice which is
due to `Extension` actually being an "indirect dependency" of
`ExtensionFolder` and therefore should be precompiled before it.

With this PR we instead get:

```julia
julia> Precompilation.precompilepkgs(; timing=true)
Precompiling all packages...
    347.5 ms  ✓ ExtDep2
    294.0 ms  ✓ SomePackage
    293.2 ms  ✓ HasExtensions
    216.5 ms  ✓ HasExtensions → LinearAlgebraExt
    554.9 ms  ✓ ExtDep
    580.9 ms  ✓ HasExtensions → Extension
    593.8 ms  ✓ HasExtensions → ExtensionFolder
    261.3 ms  ✓ HasDepWithExtensions
  8 dependencies successfully precompiled in 2 seconds

julia> Precompilation.precompilepkgs(; timing=true)

julia> 
```

`Extension` is precompiled after `ExtensionFolder` and nothing happens
on the second call.

Also, with this PR we get for the issue in
JuliaLang#53081 (comment):

```julia
(jl_zuuRGt) pkg> st
Status `/private/var/folders/tp/2p4x9ygx48sgsdl1ccg1mp_40000gn/T/jl_zuuRGt/Project.toml`
⌃ [d236fae5] PreallocationTools v0.4.17
⌃ [0c5d862f] Symbolics v5.16.1
Info Packages marked with ⌃ have new versions available and may be upgradable.

julia> Precompilation.precompilepkgs(; timing=true)
┌ Warning: Circular dependency detected. Precompilation will be skipped for:
│   SymbolicsPreallocationToolsExt
│   SymbolicsForwardDiffExt
```

and we avoid precompiling the problematic extensions.

This should also allow extensions to precompile in parallel which I
think was prevented before (from the code that is removed in the
beginning of the diff).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler:precompilation Precompilation of modules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants