Skip to content

0.2.6 G12 Lot 2 — performance-wall diagnosis

This lot diagnoses the linear_static performance wall before any solver optimization. It changes only verification infrastructure and evidence; no file under src/solveur was modified. The controlled baseline recorded by the runner is 4dc8af83d8d45d6a4d61f242aa6b1f974d87bdb3.

Machine-readable artifacts:

  • contract: qualification/0_2_6/g12_lot2_contract.json
  • scaling: qualification/0_2_6/g12_lot2_scaling.json
  • profiles: qualification/0_2_6/g12_lot2_profiles.json
  • same-domain high-order comparison: qualification/0_2_6/g12_lot2_high_order_fair.json
  • aggregate diagnosis: qualification/0_2_6/g12_lot2_diagnostic.json
  • runner: scripts/benchmark_g12_lot2.py

Harness audit

The Lot-1 nnz field was found to represent the reduced matrix resource estimate rather than the final global stiffness matrix. The Lot-1 harness and contract now report global_stiffness_nnz/nnz from the assembler's final_nnz before constraint reduction, and report reduced_stiffness_nnz separately. The corrected Lot-1 replay preserved the solution checksums and residuals.

Lot-2 timing is isolated from intrusive memory instrumentation: child timing runs do not enable the RSS sampler or tracemalloc; a parent process samples RSS separately. The previous Lot-1 approximately-10k timeout occurred with both instruments active in the timed process and is classified as HARNESS_ERROR, not as a solver resource boundary. The clean Lot-2 retest completed 10,125 actual DOF in 24.332 s.

The route reports solution checksum, residual and finite metrics for every measured run. No checksum/status/residual drift was observed, so NUMERICAL_REGRESSION_DETECTED is NO.

Controlled scaling

Route: linear_static, family: connected structured TET4, one measured run per size, timeout 120 s. The requested intermediate sizes all completed.

target DOF actual DOF global nnz reduced nnz assembly (s) sparse conversion (s) BC (s) solve (s) total wall (s) peak RSS (MiB) residual
3,000 3,000 95,172 85,252 2.671 0.013 0.00013 0.020 4.146 90.0 8.59e-12
5,000 5,184 168,924 154,360 6.558 0.027 0.00019 0.038 9.154 100.3 7.13e-12
7,500 8,232 273,348 253,252 13.804 0.041 0.00025 0.069 18.124 114.4 6.07e-12
10,000 10,125 338,727 315,532 19.296 0.045 0.00028 0.087 24.332 124.7 6.02e-12

The ordinary log-log slopes over actual DOF are approximately 1.625 for assembly, 1.685 for load assembly, 1.665 for load_balance, 1.018 for sparse conversion, 1.199 for the linear solve, and 1.458 for total wall time. These are measurements over four sizes, not extrapolation or a product guarantee.

Profile diagnosis

The cProfile runs used the same route but disabled RSS/tracemalloc in the timed child. The largest profile completed at 10,125 actual DOF in 51.106 s under cProfile; profile absolute times are not compared directly to the scaling timings because profiling itself adds overhead. Its ten most expensive entries by cumulative time were:

rank function calls cumulative (s) self (s) cumulative share
1 _run_once (diagnostic wrapper) 1 51.098 ~0 100.00%
2 _run_with_load_balance_timer (diagnostic wrapper) 1 50.957 ~0 99.72%
3 _run_with_constraint_timer (diagnostic wrapper) 1 50.957 0.006 99.72%
4 LinearStaticSolver.solve 1 50.951 ~0 99.71%
5 runner timed load-balance wrapper 230 39.427 0.002 77.16%
6 solveur.loads.integration.load_balance 230 39.425 4.559 77.15%
7 GlobalAssembler.assemble_loads 1 38.774 ~0 75.88%
8 GlobalAssembler._assemble_load_data 1 38.774 0.007 75.88%
9 numpy.core.numeric.cross 842,106 28.089 11.328 54.97%
10 MeshValidator.validate 1 8.461 0.001 16.56%

The remaining profile entries identify the same mesh-quality path: _element_quality_details (7.716 s), MeshQuality.tet_metrics (7.479 s), numpy.moveaxis (14.423 s cumulative), DOF has/normalize_dof_name, _tet_radius_ratio, and triangle-area helpers. The phase timers and the profile agree: load_balance is the primary component, mesh-quality validation is secondary, and the CG solve is not the wall.

Memory and storage

Global CSR storage grew from 1,154,068 bytes at 3,000 DOF to 4,105,228 bytes at 10,125 DOF, with a log-log slope of approximately 1.043. Global nnz/DOF grew from 31.724 to 33.455 and matrix storage/DOF from 384.7 to 405.5 bytes. Parent-sampled RSS was 90.0, 100.3, 114.4 and 124.7 MiB respectively. The RSS slope is not interpreted as allocation complexity because it includes the child process baseline and warm-up. No independent public-route counter exposes all copies/conversions; sparse conversion and unattributed solver time are recorded, and no unmeasured copy is claimed as a bottleneck.

High-order same-domain check

The fair comparison uses one unit tetrahedron (TET4/TET10) or one unit cube (HEX8/HEX20) with the same material and boundary convention. It is a resource/implementation comparison, not equal-accuracy evidence.

family DOF global nnz CSR bytes nnz/DOF CSR bytes/DOF assembly (s) solve (s)
TET4 12 72 916 6.0 76.3 0.00082 0.00132
TET10 30 690 8,404 23.0 280.1 0.00256 0.00122
HEX8 24 576 7,012 24.0 292.2 0.00365 0.00129
HEX20 60 3,600 43,444 60.0 724.1 0.03268 0.00131

The measured ratios are TET10/TET4: 3.114x assembly, 9.583x global nnz and 9.175x CSR storage; HEX20/HEX8: 8.947x assembly, 6.25x global nnz and 6.196x CSR storage. The one-element runs are too small for a performance claim beyond this controlled comparison.

Diagnosis and next-step candidates

PRIMARY_BOTTLENECK = PYTHON_ASSEMBLY, specifically repeated load_balance work inside load assembly. At the largest profiled case it consumes 39.427 s (77.15% of profiled wall), while mesh validation consumes 8.461 s (16.56%). Sparse conversion (0.055 s) and CG solve (0.096 s) are not primary bottlenecks. The clean intermediate scaling confirms completion through 10k target DOF; no solver timeout remains in this controlled range.

Candidate work is deliberately deferred to a future optimization lot:

  1. High priority: batch/vectorize load_balance and avoid a full node scan for each nodal load; the current call/visit counts and profile support this as the largest opportunity.
  2. Medium-high: cache immutable mesh-quality validation only if the product and verification contract permits it.
  3. Medium: reduce repeated DOF-name normalization/index lookup in hot loops.
  4. Low: investigate sparse assembly internals after the two Python paths; current measurements do not justify prioritizing the CG solver.

No candidate is implemented in Lot 2. No functional solver code changed.