Skip to content

Fan out compute-bound row-scan kernels from 128k rows - #424

Open
aymuos15 wants to merge 4 commits into
reflex-dev:mainfrom
aymuos15:perf/per-kernel-parallel-fan-out-thresholds
Open

Fan out compute-bound row-scan kernels from 128k rows#424
aymuos15 wants to merge 4 commits into
reflex-dev:mainfrom
aymuos15:perf/per-kernel-parallel-fan-out-thresholds

Conversation

@aymuos15

@aymuos15 aymuos15 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Fan out compute-bound row-scan kernels from 128k rows

Summary

Every row-scan kernel in src/kernels.rs shared one fan-out gate, PAR_THRESHOLD = 1 << 19 (524,288 rows). It was introduced for bin_2d/histogram, then sweep-applied to the rest from a 4-core calibration at 10M rows. The 128k–512k band was never measured, and it is where multi-chart dashboards live.

M4 and uniform histogram run ~2.5–3 ns/row serially, so an 18-way fan-out repays its ~0.25–0.3 ms spawn+join well before 512k; the shared gate strands them on one core, costing 2.5–3.6×. This gives them their own gate, PAR_THRESHOLD_COMPUTE = 1 << 17 (131,072 rows, the crossover zone maps already use). Every other kernel keeps 512k.

Simply lowering the single gate was measured and rejected: encode_f32 at 200k rows is ~5.7× slower fanned out, since spawn+join dominates a ~0.2 ns/row memory-bound pass. Hence per cost class.

Changes

  • src/kernels.rs: add the compute-class gate and generalize the routing helpers so the threshold is a parameter rather than baked into the lookup. The CodSpeed-serial check and the 18-worker cap stay in the shared helper:
 const PAR_THRESHOLD: usize = 1 << 19;
+const PAR_THRESHOLD_COMPUTE: usize = 1 << 17;

 fn par_threads(n: usize) -> usize {
+    par_threads_above(n, PAR_THRESHOLD)
+}
+
+fn par_threads_above(n: usize, threshold: usize) -> usize {
     static CODSPEED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
     if *CODSPEED.get_or_init(|| std::env::var_os("CODSPEED_ENV").is_some()) {
         return 1;
     }
     let cores = std::thread::available_parallelism().map_or(1, |p| p.get());
-    par_threads_for(n, cores)
+    par_threads_above_for(n, threshold, cores)
 }

-fn par_threads_for(n: usize, cores: usize) -> usize {
-    if n >= PAR_THRESHOLD {
+fn par_threads_above_for(n: usize, threshold: usize, cores: usize) -> usize {
+    if n >= threshold {
         cores.clamp(1, MAX_ROW_THREADS)

The two compute-bound call sites pass the compute constant inline, matching how raster_fanout already takes its per-workload threshold as an argument:

-        par_threads(end - start),
+        par_threads_above(end - start, PAR_THRESHOLD_COMPUTE),
...
-    histogram_uniform_impl(data, lo, hi, par_threads(data.len()), out)
+    histogram_uniform_impl(
+        data, lo, hi, par_threads_above(data.len(), PAR_THRESHOLD_COMPUTE), out,
+    )

Plus par_thread_gates_route_by_cost_class, a deterministic routing test across 1/4/20/64 cores: the compute gate fans out at exactly its threshold, and a compute-sized input still stays serial under the general gate. That is the invariant distinguishing the two classes.

  • spec/design/rust-engine.md: E5 updated to document per-cost-class gates with their measured ns/row and break-evens.
  • spec/benchmarks/results.md: the kernel-parallelization note now states the 128k compute gate alongside the 512k general gate.

Performance

i7-12800H (20 threads), Linux 6.8, fat-LTO release build. Interleaved base/branch rounds; medians of 15 runs after 3 warmups, ms:

case base branch speedup
M4 200k 0.548 0.229 2.4×
M4 300k 0.832 0.287 2.9×
M4 400k 1.066 0.404 2.6×
M4 500k 1.337 0.375 3.6×
histogram 200k 0.540 0.214 2.5×
histogram 300k 0.792 0.259 3.1×
histogram 500k 1.317 0.419 3.1×
8×500k-line dashboard build_payload 11.26 3.33 3.4×
300k-line build_payload 0.830 0.376 2.2×

Kernels left on the general gate are unchanged within noise (encode_f32 500k 0.124 → 0.132, min_max 500k 0.492 → 0.498, is_sorted 500k 0.247 → 0.257), confirming the re-gate is confined to the two compute-bound kernels. Below 128k rows the code path is identical to before, so small-N behavior is untouched by construction.

Testing

  • cargo test --release → green, including the new routing test and the existing parity fuzz (serial and fanned-out results bitwise identical).
  • cargo clippy --all-targets, cargo fmt --check, ruff check, ruff format --check → clean. ty check → unchanged from base; no Python touched.
  • pytest → green apart from two failures that reproduce on an unmodified base checkout (packaging-layout drift, unrelated).
  • Payloads byte-identical to base (spec+blob SHA-256) for a 300k decimated line, 300k bar, and 150k scatter.

Risks & rollout

  • Determinism is unaffected. Thresholds select segment count only; segment structure and results are thread-count invariant, covered by the existing parity fuzz. No rendered output can change.
  • No ABI change: thresholds are internal routing, so no ABI_VERSION bump and no Python or JS changes.
  • CodSpeed stays serial: the env check lives in the shared helper, so benchmark instrumentation is unchanged.

Summary by CodeRabbit

  • Performance Improvements

    • Improved parallel processing for compute-intensive scans by enabling it at lower row counts.
    • Improved histogram performance while limiting parallel workers for high bin counts.
    • Preserved efficient serial behavior when parallel processing would not provide benefits.
  • Documentation

    • Updated benchmark and engine guidance to describe distinct parallelization thresholds and worker limits.

Every row-scan kernel shared PAR_THRESHOLD = 1<<19, introduced for
bin_2d/histogram and sweep-applied to the rest in 84ff460 (calibrated on
a 4-core sandbox at 10M rows; the 131k-512k band never measured). M4 and
uniform histogram run ~2.5-3 ns/row serially, so a scoped 18-way fan-out
(~0.25-0.3 ms spawn+join) pays for itself from ~128k rows - the crossover
zone maps already use (two 65,536-row chunks).

Split the gate per cost class: par_compute_threads (128k) for m4_indices
and histogram_uniform; par_threads keeps the general 512k gate for
everything else. Routing stays bitwise-deterministic - thresholds choose
segment count only, covered by the existing thread-count parity fuzz.

Measured on an i7-12800H (20 threads), interleaved medians: m4 300k
0.82-0.98 -> 0.31-0.47 ms, m4 500k 1.34-1.59 -> 0.33-0.54 ms; histogram
similar (2-4x across the band); 8x500k-line dashboard build_payload
11.1-13.8 -> 3.5-5.2 ms. Payloads byte-identical to main (spec+blob
SHA-256) for decimated line, bar, and direct scatter.

Encode/normalize re-gating was tried and reverted: normalize is ~1.2
ns/row (regresses 2-4x at a 2M gate) and encode's crossover is
machine-state-dependent, so both keep the 512k gate.
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 57bc5e04-174c-419d-8044-358a2692e39c

📥 Commits

Reviewing files that changed from the base of the PR and between c11d71b and 226afcf.

📒 Files selected for processing (1)
  • src/kernels.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/kernels.rs

📝 Walkthrough

Walkthrough

The kernel parallelism logic now separates compute-bound and general scan thresholds. M4 decimation and uniform histograms use the 128k threshold. Histogram worker counts also respect points-per-bin limits. Tests and documentation cover the updated behavior.

Changes

Kernel parallelism thresholds

Layer / File(s) Summary
Threshold routing
src/kernels.rs
Worker selection accepts explicit thresholds. M4 decimation and uniform histograms use the 128k compute threshold. Histograms limit workers by points per bin.
Validation and threshold guidance
src/kernels.rs, spec/benchmarks/results.md, spec/design/rust-engine.md
Tests cover threshold routing, serial high-bin behavior, and histogram output parity. Documentation records the 128k compute threshold, 512k general threshold, and accumulator limits.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: enabling compute-bound row-scan kernels to parallelize from 128k rows.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/kernels.rs (1)

6881-6892: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add parity coverage for the newly reachable kernel paths.

The added test calls only par_threads_above_for. It does not execute m4_indices or histogram_uniform at PAR_THRESHOLD_COMPUTE - 1 and PAR_THRESHOLD_COMPUTE. Add serial-versus-dispatched comparisons for both kernels, including exact histogram output equality.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/kernels.rs` around lines 6881 - 6892, Extend
par_thread_gates_route_by_cost_class to invoke both m4_indices and
histogram_uniform at PAR_THRESHOLD_COMPUTE - 1 and PAR_THRESHOLD_COMPUTE,
comparing serial and dispatched results. For histogram_uniform, assert exact
equality of the complete histogram outputs while preserving the existing
threshold and core-count coverage.
spec/benchmarks/results.md (1)

742-745: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Mention the grid-aware bin_2d gate.

bin_2d uses the 512k row threshold as a base gate, but it also applies a points-per-cell rule. src/kernels.rs, Line 6868, keeps bin_2d serial above that threshold when the grid is large. State that the 512k value is not an unconditional fan-out threshold.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/benchmarks/results.md` around lines 742 - 745, Update the benchmark
description near the per-cost-class gates to explicitly identify the grid-aware
bin_2d gate: 512k rows is its base threshold, but parallel fan-out also depends
on the points-per-cell rule, with large grids remaining serial above that
threshold as implemented by bin_2d. Clarify that 512k is not an unconditional
fan-out threshold.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/kernels.rs`:
- Around line 4606-4612: Update the worker-count selection passed to
histogram_uniform_impl so parallel fan-out accounts for both data.len() and
out.len(), limiting workers when the bin count makes per-worker scratch
allocation or merge cost excessive. Preserve the existing serial behavior for
large-bin histograms, and add a regression test covering an out.len() value in
the 128k–512k range.

---

Nitpick comments:
In `@spec/benchmarks/results.md`:
- Around line 742-745: Update the benchmark description near the per-cost-class
gates to explicitly identify the grid-aware bin_2d gate: 512k rows is its base
threshold, but parallel fan-out also depends on the points-per-cell rule, with
large grids remaining serial above that threshold as implemented by bin_2d.
Clarify that 512k is not an unconditional fan-out threshold.

In `@src/kernels.rs`:
- Around line 6881-6892: Extend par_thread_gates_route_by_cost_class to invoke
both m4_indices and histogram_uniform at PAR_THRESHOLD_COMPUTE - 1 and
PAR_THRESHOLD_COMPUTE, comparing serial and dispatched results. For
histogram_uniform, assert exact equality of the complete histogram outputs while
preserving the existing threshold and core-count coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f850d5c1-353f-476d-bd09-7632937cd236

📥 Commits

Reviewing files that changed from the base of the PR and between eb37670 and b8ae741.

📒 Files selected for processing (3)
  • spec/benchmarks/results.md
  • spec/design/rust-engine.md
  • src/kernels.rs

Comment thread src/kernels.rs Outdated
@codspeed-hq

codspeed-hq Bot commented Aug 1, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 109 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing aymuos15:perf/per-kernel-parallel-fan-out-thresholds (226afcf) with main (eb37670)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/kernels.rs">

<violation number="1" location="src/kernels.rs:4610">
P2: Lowering the fan-out gate for histogram_uniform to 128k rows (PAR_THRESHOLD_COMPUTE) only accounts for row count, not bin count. Since histogram_uniform_impl allocates one full bin vector per worker and merges with O(threads * n_bins) cost, a call with a large `out.len()` in the newly-lowered 128k–512k row range can now spawn up to MAX_ROW_THREADS per-worker bin allocations that were previously avoided, risking excessive memory use or a merge cost that outweighs the parallel speedup. Consider factoring bin count into the thread-count decision (e.g., a combined row*bin scratch-budget check) and add a regression test for large `out.len()` in this range.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread src/kernels.rs Outdated
data,
lo,
hi,
par_threads_above(data.len(), PAR_THRESHOLD_COMPUTE),

@cubic-dev-ai cubic-dev-ai Bot Aug 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Lowering the fan-out gate for histogram_uniform to 128k rows (PAR_THRESHOLD_COMPUTE) only accounts for row count, not bin count. Since histogram_uniform_impl allocates one full bin vector per worker and merges with O(threads * n_bins) cost, a call with a large out.len() in the newly-lowered 128k–512k row range can now spawn up to MAX_ROW_THREADS per-worker bin allocations that were previously avoided, risking excessive memory use or a merge cost that outweighs the parallel speedup. Consider factoring bin count into the thread-count decision (e.g., a combined row*bin scratch-budget check) and add a regression test for large out.len() in this range.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/kernels.rs, line 4610:

<comment>Lowering the fan-out gate for histogram_uniform to 128k rows (PAR_THRESHOLD_COMPUTE) only accounts for row count, not bin count. Since histogram_uniform_impl allocates one full bin vector per worker and merges with O(threads * n_bins) cost, a call with a large `out.len()` in the newly-lowered 128k–512k row range can now spawn up to MAX_ROW_THREADS per-worker bin allocations that were previously avoided, risking excessive memory use or a merge cost that outweighs the parallel speedup. Consider factoring bin count into the thread-count decision (e.g., a combined row*bin scratch-budget check) and add a regression test for large `out.len()` in this range.</comment>

<file context>
@@ -4596,7 +4603,13 @@ pub fn stratified_sample_mask<T: Copy + Sync + Into<u64>>(
+        data,
+        lo,
+        hi,
+        par_threads_above(data.len(), PAR_THRESHOLD_COMPUTE),
+        out,
+    )
</file context>
Fix with cubic

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be addrsssedin recent commits

The kernel-parallelization note listed bin_2d among the general scans that
"fan out from 512k", which reads as an unconditional threshold. It is a base
gate: bin_2d_threads also tracks points per cell and keeps a large grid serial
above 512k, because the per-thread grids and their merge dwarf the scan
(asserted in kernels.rs: bin_2d_threads(1 << 20, 1 << 20) == 1).

Raised in CodeRabbit review of reflex-dev#424:
reflex-dev#424 (review)
histogram_uniform_impl gives every worker its own vec![0u64; n_bins] and
merges at O(threads * n_bins). Neither term shrinks with the row count, so
gating on data.len() alone under-counts the cost of fanning out: with enough
bins the merge outweighs the scan it parallelizes. Base had this too -- its
512k row gate ignored out.len() as well -- but lowering the compute gate to
128k amortizes the same merge over ~4x fewer rows, moving the crossover where
fan-out stops paying down with it.

Cap workers at points per bin, mirroring bin_2d_threads, whose per-thread
grids are this kernel's per-thread bins. A bin vector as large as the input
now stays serial; scratch is bounded at threads * n_bins <= n counters.

Ordinary bin counts are unaffected: at 128k rows and the 512 bins the
benchmarks use, the ratio is 256 and clamps to the same 18-worker cap, so the
histogram numbers measured for this PR stand.

Raised by both review bots on reflex-dev#424:
reflex-dev#424 (comment)
reflex-dev#424 (comment)
@aymuos15

aymuos15 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Both bots caught the same thing. histogram_uniform gated on rows only, but each worker owns a vec![0u64; n_bins] with an O(threads * n_bins) merge, so enough bins makes fan-out cost more than the scan.

Fixed in c11d71b by capping workers at points per bin, mirroring bin_2d_threads:

(n / n_bins.max(1)).clamp(1, par_threads_above(n, PAR_THRESHOLD_COMPUTE))

Normal bin counts are untouched. At 128k rows and the benchmarks' 512 bins the ratio clamps to the same 18-worker cap, so the PR's numbers stand. Tests cover routing and 128k-512k parity; specs updated.

Nitpicks: bin_2d spec wording fixed in 7c59e08.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/kernels.rs">

<violation number="1" location="src/kernels.rs:2505">
P2: M4 calls with `n_buckets == 1` now enter the parallel splitter from 128k rows even though only one segment can exist. Each candidate boundary is linearly advanced to `end`, adding roughly `threads / 2` extra full scans; capping workers by `n_buckets` avoids this regression.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread src/kernels.rs Outdated
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/kernels.rs">

<violation number="1" location="src/kernels.rs:2505">
P2: Clustered or duplicate-x M4 windows in the new 128k–512k fan-out band can regress badly: every proposed split scans through the same giant bucket, then only one segment is executed. Consider finding bucket boundaries with a single pass/binary search, or falling back before paying these repeated scans.</violation>

<violation number="2" location="src/kernels.rs:2505">
P3: The per-cost-class routing docs updated in this PR enumerate the worker caps for `bin_2d` (points per cell) and `histogram` (points per bin), but the newly added `m4` cap at `n_buckets` is not documented. Since m4's parallel path only ever splits at bucket boundaries (at most `n_buckets` distinct segments are possible), capping workers at `n_buckets` is a benign optimization — but it is freshly introduced routing behavior. Per the repo's spec-tracking contract (AGENTS.md: "Every decimation/tier decision is recorded in the spec, never silent"), it would be worth one sentence alongside the existing accumulator-cap note in `spec/design/rust-engine.md` (E5) and `spec/benchmarks/results.md` so the documented fan-out contract matches the implementation.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread src/kernels.rs
start,
end,
par_threads(end - start),
par_threads_above(end - start, PAR_THRESHOLD_COMPUTE).min(n_buckets),

@cubic-dev-ai cubic-dev-ai Bot Aug 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Clustered or duplicate-x M4 windows in the new 128k–512k fan-out band can regress badly: every proposed split scans through the same giant bucket, then only one segment is executed. Consider finding bucket boundaries with a single pass/binary search, or falling back before paying these repeated scans.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/kernels.rs, line 2505:

<comment>Clustered or duplicate-x M4 windows in the new 128k–512k fan-out band can regress badly: every proposed split scans through the same giant bucket, then only one segment is executed. Consider finding bucket boundaries with a single pass/binary search, or falling back before paying these repeated scans.</comment>

<file context>
@@ -2502,7 +2502,7 @@ pub fn m4_indices(x: &[f64], y: &[f64], x0: f64, x1: f64, n_buckets: usize) -> V
         start,
         end,
-        par_threads(end - start),
+        par_threads_above(end - start, PAR_THRESHOLD_COMPUTE).min(n_buckets),
     )
 }
</file context>
Fix with cubic

Comment thread src/kernels.rs
start,
end,
par_threads(end - start),
par_threads_above(end - start, PAR_THRESHOLD_COMPUTE).min(n_buckets),

@cubic-dev-ai cubic-dev-ai Bot Aug 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The per-cost-class routing docs updated in this PR enumerate the worker caps for bin_2d (points per cell) and histogram (points per bin), but the newly added m4 cap at n_buckets is not documented. Since m4's parallel path only ever splits at bucket boundaries (at most n_buckets distinct segments are possible), capping workers at n_buckets is a benign optimization — but it is freshly introduced routing behavior. Per the repo's spec-tracking contract (AGENTS.md: "Every decimation/tier decision is recorded in the spec, never silent"), it would be worth one sentence alongside the existing accumulator-cap note in spec/design/rust-engine.md (E5) and spec/benchmarks/results.md so the documented fan-out contract matches the implementation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/kernels.rs, line 2505:

<comment>The per-cost-class routing docs updated in this PR enumerate the worker caps for `bin_2d` (points per cell) and `histogram` (points per bin), but the newly added `m4` cap at `n_buckets` is not documented. Since m4's parallel path only ever splits at bucket boundaries (at most `n_buckets` distinct segments are possible), capping workers at `n_buckets` is a benign optimization — but it is freshly introduced routing behavior. Per the repo's spec-tracking contract (AGENTS.md: "Every decimation/tier decision is recorded in the spec, never silent"), it would be worth one sentence alongside the existing accumulator-cap note in `spec/design/rust-engine.md` (E5) and `spec/benchmarks/results.md` so the documented fan-out contract matches the implementation.</comment>

<file context>
@@ -2502,7 +2502,7 @@ pub fn m4_indices(x: &[f64], y: &[f64], x0: f64, x1: f64, n_buckets: usize) -> V
         start,
         end,
-        par_threads(end - start),
+        par_threads_above(end - start, PAR_THRESHOLD_COMPUTE).min(n_buckets),
     )
 }
</file context>
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant