# Solver optimization audit Measured on an Intel Core i5-12600K, Linux, GCC 16.2.1, Release (`-O3`, no fast-math). Reference is commit `589d22a`. Performance results are specific to this machine; Windows/MSVC runtime measurements remain pending. The retained changes use portable C++ and compiler vectorization, with no new CPU requirement. ## Retained changes * Boundary solves start from the previous converged face pressure when it is inside the new physical bracket. Otherwise they start at the bracket midpoint. The safeguarded Newton solver, residual tolerance, iteration cap, choking, reverse flow, donor budgets and inertance calculation remain unchanged. * Pipe preparation computes primitive states and wave speeds in an independent loop, then validates states and reduces the CFL timestep. GCC confirms 128-bit vectorization of the arithmetic loop. Invalid states still throw before flux updates. No approximate reciprocal/square root or relaxed arithmetic is used. * Pipe fresh-air tracer fractions smaller than the smallest **normal** float (approximately 1.175e-38) are rounded to zero. Previously, purged intake/exhaust tracer tails persisted as subnormals, causing severe sustained-load slowdown. This affects species attribution below that precision, not total density, momentum or energy. The regression suite checks bulk conservation and retention of normal tracer concentrations. Global flush-to-zero modes are not enabled. Starting guesses change numerical trajectories within the existing convergence tolerance; long engine/audio traces need not be bit-identical to the reference. ## Profiling and experiments A `-pg` build running every scenario for two simulated seconds attributed about 39% of sampled executable time to pipe advancement, 38% to orifice boundary solves, and 13% to Solver::step (including inlined preparation). Outdoor emitter rendering was about 1%. These are sampled gprof results, not precise attribution of time inside dynamically linked math functions. `fluidsim_thread_benchmark` now uses each scenario's actual pipe geometry and compares persistent 1/2/4-worker execution with barriers per substep. This isolates the parallelizable pipe work; boundary/component work would remain serial. Every current scenario loses with workers, so production remains serial. The synthetic 64-pipe, 256-cells-per-pipe case benefits from workers, confirming that this strategy has a useful scale threshold. It does not justify adding its scheduling and synchronization cost to the current small graphs. SIMD-disabled and `-march=native` builds are comparison experiments. Native compilation is not enabled in the project defaults, since the executable could then require instructions missing on another machine. ## Results Mean wall seconds per simulated second, six-second sustained-load runs: | Scenario | Reference | Optimized | Time reduction | SIMD disabled | |---|---:|---:|---:|---:| | Twin | .528 | .405 | 23% | .423 | | B205 | .884 | .689 | 22% | .737 | | Two-stroke racing | .561 | .470 | 16% | .535 | | Two-stroke straight | .539 | .464 | 14% | .525 | | Helmholtz | .167 | .141 | 16% | .160 | | Pulse | .256 | .238 | 7% | .315 | Idle/transient optimized means (seconds per simulated second): twin .394/.399, B205 .666/.683, racing two-stroke .450/.459, straight two-stroke .452/.453, Helmholtz .141/.140, and pulse .238/.238. The pulse scenario has no orifice, so its improvement isolates the SIMD-friendly preparation change. Disabling SIMD increases time in every scenario. An optional native-instruction build improves some timings further, but is not shipped as the default: FMA also changes rounding and the binary is CPU-specific. Initial isolated changes measured approximately .40 seconds for the twin after boundary warm starts alone (previously .53). The sustained high-load scenario still took about 1.2 seconds after that change; eliminating subnormal tracer tails brought it to about .65. The diagnostic found 22 subnormal pipe fields before truncation and zero afterwards. This is distinct from reducing CFL substeps or geometry; neither was changed. Six-second idle and throttle/release runs also complete for every scenario. These are finite test windows, not a guarantee of real-time performance for every user configuration or arbitrarily long simulation. ## Reproduction Final validation at the time of this audit: all nine then-registered CTest suites passed both with SIMD enabled (28.50 s) and disabled (30.86 s). The fluid validation executable includes 30 checks, including tracer underflow conservation. The Release SFML desktop target also rebuilt successfully. This audit verifies desktop compilation and headless audio rendering; interactive playback underrun testing remains a separate user check. ```sh cmake -S . -B build-opt -DCMAKE_BUILD_TYPE=Release -DFLUIDSIM_BUILD_DESKTOP=OFF cmake --build build-opt -j ctest --test-dir build-opt --output-on-failure ./build-opt/fluidsim_scenario_benchmark 6 loaded ./build-opt/fluidsim_scenario_benchmark 6 idle ./build-opt/fluidsim_scenario_benchmark 6 transient ./build-opt/fluidsim_thread_benchmark ``` The scenario benchmark runs twin, B205, V8, racing and straight-pipe two-strokes, Helmholtz and pulse scenarios, including their normal audio path. `loaded` holds four-strokes at 7,000 RPM and two-strokes at 10,000 RPM using an ideal speed-holding dyno; engine throttle is 70%. `idle` releases the pedal; `transient` starts released, applies 70%, then releases for the last two seconds. The optional final argument selects one scenario, e.g. `6 loaded v8`. CSV timing/checksum goes to stdout; subnormal and iteration diagnostics go to stderr. Inspection happens outside the timed sample-processing loop. Use `-DFLUIDSIM_ENABLE_SIMD=OFF` in a separate build for scalar comparisons. Run timed experiments sequentially, without concurrent builds or simulations. The raw final measurements are retained in `verification/optimization/`.