Files
FluidSimCpp/verification/OPTIMIZATION.md
2026-10-23 18:29:51 +02:00

5.9 KiB

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.

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/.