Restore and modernize the precompile workload - #49
Merged
ChrisRackauckas merged 1 commit intoAug 9, 2026
Conversation
The `@setup_workload` block at the bottom of the module has been inside a `#= ... =#` since before 0.2.0, and PrecompileTools was not in [deps], so no kernel precompiled at all. The block also could not be uncommented as-is: it calls `__init__()`, which this module does not define. The restored workload covers all four wrapper families (both `rdiv!` and `ldiv!` respecialize per family because `ldiv!` transposes its arguments), unit and non-unit diagonals, the `Val(true)`/`Val(false)` thread variants, both `Float32` and `Float64`, and the vector right-hand-side kernels added in 0.2.5. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #49 +/- ##
=======================================
Coverage 95.22% 95.22%
=======================================
Files 1 1
Lines 942 942
=======================================
Hits 897 897
Misses 45 45 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
@setup_workloadblock at the bottom ofsrc/TriangularSolve.jlhas been sitting inside a#= ... =#since before 0.2.0, andPrecompileToolswas not in[deps]. Nothing in this package precompiled. It also could not simply be uncommented: the block calls__init__(), and this module defines no__init__. Uncommenting it verbatim and precompiling givesThis restores it, modernizes it to the current API (the old block knew only about
rdiv!-upper andldiv!-lower matrix solves; the native lower-rdiv!/upper-ldiv!matrix drivers and the 0.2.5 vector kernels did not exist yet), and addsPrecompileToolsto[deps]/[compat].Why N = 8 is enough
div_dispatch!,div_dispatch_L!and_naive_vsolve!branch on runtime sizes, so type inference — which is whole-body — already covers the blocked, threaded and remainder branches from a single small call. A separate blocked-size workload case caches nothing extra. What is not free is anything that changes a type: the eltype, the wrapper (eachldiv!transposes its arguments, so the drivers respecialize), the unit diagonal, and theVal(true)/Val(false)thread flag. The workload is exactly the cross product of those.Coverage, before and after
TTFX = in-process
@elapsedof the first call in a fresh process, min over 5 processes,n = 8, Julia 1.12.6. Before = this branch's merge-base (identical to registered 0.2.5).rdiv!(C, A, UpperTriangular, Val(false))rdiv!(C, A, UpperTriangular)(threaded)rdiv!(C, A, UnitUpperTriangular, Val(false))ldiv!(C, LowerTriangular, A, Val(false))ldiv!(C, LowerTriangular, A)(threaded)rdiv!(C, A, LowerTriangular, Val(false))rdiv!(C, A, LowerTriangular)(threaded)ldiv!(C, UpperTriangular, A, Val(false))ldiv!(C, UpperTriangular, A)(threaded)ldiv!(UpperTriangular, b::Vector)ldiv!(LowerTriangular, b::Vector)ldiv!(c, UnitLowerTriangular, b::Vector)Every matrix entry point was previously uncached; the vector entry points (new in 0.2.5) too. Nothing in the table was covered before.
The cache reaches downstream unchanged
RecursiveFactorization'sNotIPIVbacksolve and LinearSolve's RFLU_rf_ldiv!both call these kernels with a plainMatrixfactor, which is exactly what the workload caches. Holding RecursiveFactorization at the registered 0.2.29 and swapping only TriangularSolve:ldiv!(F::LU{Float64,..,NotIPIV}, b::Vector)ldiv!(F::LU{Float64,..,NotIPIV}, B::Matrix)ldiv!(F::LU{Float32,..,NotIPIV}, b::Vector)ldiv!(F::LU{Float32,..,NotIPIV}, B::Matrix)No change to RecursiveFactorization was needed for that.
Cost
Package precompile wall time, and the pkgimage the workload produces. Precompile numbers are the min over 7 alternating off/on rounds on a loaded 128-core box, so treat them as upper bounds on the delta.
@elapsed using TriangularSolve(min of 5)Same thing measured the other way, on a genuinely cold
JULIA_DEPOT_PATH(34 dependencies, nothing cached):So: +49 s precompile, once per depot/version; +0.108 s load, per process; −3.4 to −9.5 s on the first solve, per process. Load time is the only recurring cost and it is 30-90x smaller than the TTFX it buys back on any covered path.
Nothing was dropped as not paying for itself. The two levers a reviewer might want to pull, with their measured price:
eltype <: Union{Float32, Float64}, so this is a first-class path.RecursiveFactorization'sNotIPIVbacksolve and LinearSolve's RFLU_rf_ldiv!take, both of which call them with a plainMatrix— exactly the specializations cached here.Verification
Both include the Aqua testset (
Compat bounds,Piracy,Persistent tasksall pass — the newPrecompileToolsentry carries a[compat]bound).Downstream smoke check, since this PR adds a dependency: with this branch and RecursiveFactorization 0.2.30 both
deved into one environment,RecursiveFactorization.lu(A, Val(false))followed byldiv!(F, b)/ldiv!(F, B)at n = 100 gives relative residuals 4.8e-16 / 4.5e-16 (Float64) and 2.3e-7 / 2.8e-7 (Float32).Formatting: the added block is a fixed point of
JuliaFormatter.format_textunder this repo's.JuliaFormatter.toml(indent = 2, margin = 80, ...). I deliberately did not run the formatter over the whole file — the JuliaFormatter version available here reflows a lot of pre-existing code, and that mechanical churn does not belong in this diff.Not verified
typosreports 8 pre-existing hits on the identifierNdinsrc/TriangularSolve.jl(it wantsAnd). They are on master and untouched here; this repo has notyposCI job.pick_vector_width(T)-dependent, so the precompiled specializations are host-specific by construction (as they already were).Pkg.test()precompiles a second time under--check-bounds=yes, so the +49 s is paid twice in a test run.🤖 Generated with Claude Code