Skip to content

Stop sorting a whole column to size error-bar caps - #264

Merged
Alek99 merged 1 commit into
mainfrom
alek/errorbar-payload-cost
Jul 24, 2026
Merged

Stop sorting a whole column to size error-bar caps#264
Alek99 merged 1 commit into
mainfrom
alek/errorbar-payload-cost

Conversation

@Alek99

@Alek99 Alek99 commented Jul 24, 2026

Copy link
Copy Markdown
Member

What

test_first_payload_errorbar_large is the most expensive benchmark in the suite (~557 ms on CodSpeed; nothing else on main is close). Two thirds of it is work neither answer needs.

_auto_cap_size wants the median adjacent gap between distinct positions, and reached for np.unique — which sorts unconditionally, an O(N log N) pass over the full column. Error-bar positions are usually an ordered independent variable, so one O(N) diff both proves the column is already non-decreasing and yields those gaps directly; only an out-of-order column still pays for the sort. Same distinct values in the same order, so the median is identical.

_zero_baseline_anchor compacted base/value down to their finite rows before asking three all-or-nothing questions about them, allocating and copying two full columns per axis per build. Each question is really "does any row violate this?", which the finite mask answers in place. NaN rows are excluded by the mask exactly as the compaction excluded them.

Numbers

Benchmark shape (1M points, yerr=1.0, decimated tier), min-of-3:

before after
first payload 48.07 ms 14.09 ms -70.7%
_auto_cap_size (1M sorted positions) 27.48 ms 2.92 ms -89.4%
_zero_baseline_anchor (3M rows) 4.44 ms 1.99 ms -55.2%

Neither of these is benchmark-only work. Cap sizing runs for every auto-cap error bar, and the zero-baseline probe runs for every bar and histogram build, so the win follows real charts.

Correctness

Both functions were checked against their previous implementations, not just spot-checked:

  • _auto_cap_size returns identically over sorted, unsorted, descending, duplicate-heavy, all-equal, single-element, empty, NaN-bearing and -0.0-bearing columns.
  • _zero_baseline_anchor returns identically over all-positive, all-negative, mixed-sign, nonzero-base, NaN-bearing, all-NaN and all-zero columns.

Full Python suite: 2222 passed, 4 skipped. ruff check and ruff format --check clean.

Summary by CodeRabbit

  • Bug Fixes
    • Improved plot baseline detection for traces containing non-finite data.
    • Improved automatic error-bar cap sizing, including more efficient handling of ordered positions and repeated values.
    • Preserved appropriate spacing calculations for irregular or unordered data.

@coderabbitai

coderabbitai Bot commented Jul 24, 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: b9619a59-290f-438f-a840-cd2fb349c4b7

📥 Commits

Reviewing files that changed from the base of the PR and between 119a1ce and 2e4b8ad.

📒 Files selected for processing (2)
  • python/xy/_figure.py
  • python/xy/marks.py

📝 Walkthrough

Walkthrough

Changes

Plotting heuristics

Layer / File(s) Summary
Finite baseline anchoring
python/xy/_figure.py
_zero_baseline_anchor evaluates finite rows with masked predicates while preserving the existing "lo" and "hi" decisions.
Automatic error-bar cap spacing
python/xy/marks.py
_auto_cap_size uses O(N) adjacent gaps for non-decreasing finite positions and retains the distinct-values fallback otherwise.

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

Suggested reviewers: farhanaliraza, masenf, carlosabadia

🚥 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 accurately describes the error-bar cap sizing optimization, which is a real part of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch alek/errorbar-payload-cost

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

@codspeed-hq

codspeed-hq Bot commented Jul 24, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 25.53%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 101 untouched benchmarks
⏩ 2 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
test_first_payload_errorbar_large 769.7 ms 613.1 ms +25.53%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing alek/errorbar-payload-cost (2e4b8ad) with main (119a1ce)

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.

`test_first_payload_errorbar_large` is the most expensive benchmark in the
suite (~557 ms on CodSpeed). Two thirds of it is work neither answer needs.

`_auto_cap_size` wants the median adjacent gap between distinct positions,
and reached for `np.unique` — which sorts unconditionally, an O(N log N)
pass over the full column. Error-bar positions are usually an ordered
independent variable, so one O(N) diff both proves the column is already
non-decreasing and yields those gaps directly; only an out-of-order column
still pays for the sort. Same distinct values in the same order, so the
median is identical.

`_zero_baseline_anchor` compacted `base`/`value` down to their finite rows
before asking three all-or-nothing questions about them, allocating and
copying two full columns per axis per build. Each question is really "does
any row violate this?", which the finite mask answers in place. NaN rows
are excluded by the mask exactly as the compaction excluded them.

Measured on the benchmark's shape (1M points, yerr=1.0, decimated tier),
min-of-3:

  first payload   48.07 ms -> 14.09 ms   -70.7%
  _auto_cap_size  27.48 ms ->  2.92 ms   -89.4%  (1M sorted positions)
  _zero_baseline   4.44 ms ->  1.99 ms   -55.2%  (3M rows)

Both sit on real build paths, not just the benchmark: cap sizing runs for
every auto-cap error bar, and the zero-baseline probe runs for every bar
and histogram build.

`_auto_cap_size` returns identically over sorted, unsorted, descending,
duplicate-heavy, all-equal, single, empty, NaN-bearing and -0.0-bearing
columns; `_zero_baseline_anchor` over all-positive, all-negative, mixed,
nonzero-base, NaN-bearing, all-NaN and all-zero columns.
@Alek99
Alek99 force-pushed the alek/errorbar-payload-cost branch from c28a404 to 2e4b8ad Compare July 24, 2026 22:37
@Alek99
Alek99 merged commit 63c0697 into main Jul 24, 2026
28 checks passed
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