Skip to main content

ROOT_BATCH_MIN_PROBLEMS

Constant ROOT_BATCH_MIN_PROBLEMS 

Source
pub const ROOT_BATCH_MIN_PROBLEMS: usize = 256;
Expand description

Problem count below which a ComputeBackend::CpuMulti request runs the iterative root-finding kernels (solve_bracketed_batch, solve_newton_batch) on the calling thread instead.

§Measured crossover

Methodology. Measured 2026-08-12 on this workspace’s development machine, std::thread::available_parallelism() = 4, release build, --features parallel, rayon’s global pool, otherwise-idle machine. Batches of n independent problems x^2 - k_i = 0 with k_i spread over [1, 3) so that per-lane iteration counts differ, bracket [0, 4], RootMethod::Brent, RootSettings::default. Best of 7 samples per point, reported as wall clock for one whole batch. Produced by the #[ignore]d root_batch_crossover_benchmark test in parallel/tests.rs and transcribed from its printed output. The cheap closure is the two-flop residual above; the costly closure adds a ln/exp/sqrt chain per evaluation, standing in for a JANAF enthalpy. The serial columns are from the first run; the (repeat) columns are the speed-ups from a second, independent run of the same benchmark, carried alongside rather than averaged away because the parallel column is far noisier than the serial one.

Problemscheap serialcheap multispeed-up(repeat)costly serialcostly multispeed-up(repeat)
162.87 us34.77 us0.08x0.07x11.21 us42.19 us0.27x0.93x
326.67 us36.60 us0.18x0.71x19.32 us43.55 us0.44x1.35x
6412.99 us45.16 us0.29x0.29x42.85 us62.99 us0.68x0.83x
12825.96 us57.24 us0.45x0.59x84.88 us86.65 us0.98x1.26x
25652.94 us42.79 us1.24x2.28x175.37 us126.70 us1.38x3.18x
512108.80 us68.63 us1.59x2.91x356.78 us170.71 us2.09x2.04x
1 024201.83 us127.52 us1.58x2.01x695.26 us284.70 us2.44x2.17x
4 096805.49 us501.42 us1.61x3.63x2774.06 us992.73 us2.79x3.58x
16 3843338.49 us1173.15 us2.85x2.71x11423.08 us3916.59 us2.92x2.83x
65 53613730.89 us5071.05 us2.71x2.94x45778.24 us16260.96 us2.82x3.02x

Result. 256 is the smallest size at which neither closure lost in either run — cheap 1.24x and 2.28x, costly 1.38x and 3.18x — and it is the value this constant takes. At 128 the cheap closure lost both runs (0.45x, 0.59x) while the costly one was a coin flip (0.98x, 1.26x), which is the expected shape: a more expensive residual crosses over earlier because there is more work per lane to amortise the same dispatch cost. The honest headline is therefore that the crossover depends on the caller’s residual, and this single number is set for the cheapest residual a caller is likely to pass, so that it is safe for both.

Interpretation. This is 16x lower than the crate-wide placeholder crate::compute::CPU_MULTI_MIN_WORK_ITEMS (4 096) and 512x lower than crate::fields::parallel::FIELD_PARALLEL_CROSSOVER (131 072), and the reason is structural rather than incidental. Field algebra does one or two flops per element loaded and is memory-bandwidth bound, so extra cores buy almost nothing; a root solve does tens of iterations of arithmetic per lane against a handful of loaded bytes, so it is compute bound and threads scale on it. Keeping the crate-wide placeholder here would forfeit a measured 1.6x-2.4x on batches of 1 024.

§Relationship to crate::compute::CPU_MULTI_MIN_WORK_ITEMS

That constant documents itself as an unmeasured placeholder and invites per-kernel overrides. This is that override for the iterative root finders. It is now the fourth measured value in the crate, and the spread across the four (256, 4 096, 131 072, 262 144 — a factor of 1 024) is further evidence that no single crate-wide threshold can be right. Offered as input to bead op-yvj.4.7.

§Limitations

One machine, four logical cores, one closure family, idle. Not measured on Android/Termux hardware and not on a many-core server. The crossover moves with the caller’s residual cost, so a caller that knows its own closure is expensive is better off calling the kernel with an explicit ComputeBackend than trusting a single number.

§Units

A count of independent root problems, dimensionless.