Skip to content

chore: add pytest-benchmem benchmark suite with PR head-vs-base reporting#108

Closed
FBumann wants to merge 4 commits into
mainfrom
feat/benchmarks
Closed

chore: add pytest-benchmem benchmark suite with PR head-vs-base reporting#108
FBumann wants to merge 4 commits into
mainfrom
feat/benchmarks

Conversation

@FBumann

@FBumann FBumann commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • benchmarks/ — pytest-benchmem suite for aggregate() focused on the production config (hierarchical, Distribution(scope="global"), include_period_sums=False, extremes replace): a deterministic representation × extremes grid, the representation × columns interaction, preserve_column_means on/off, and column/slice/day scaling under the full config.
  • .github/workflows/benchmarks.yaml — adapted from fluxopt's benchmark-hint workflow: runs the suite on PR head and base in the same job, keeps one sticky PR comment with the benchmem compare --diff table (walltime + memray peak), writes it to the step summary, and uploads an interactive benchmem plot artifact. continue-on-error, so it never blocks a merge.
  • .benchmarks/ gitignored — local baselines are machine-specific.

Baseline findings that motivated the case selection

  • Distribution(scope="global") is the only expensive focus option: 2–2.6× over medoid at 8 columns, 3× at 128 columns (durationRepresentation's global branch is an unvectorized per-attribute loop — upstream tsam issue).
  • extremes replace, dist_cluster, mean, and preserve_column_means are all free within noise.
  • Slices scale exactly linearly (sequential loop); n_clusters is flat.

Cases are a deterministic grid rather than fuzzed because benchmem compare matches runs by test ID, so IDs must be stable for baselines and the PR diff to pair up.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added automated CI benchmarks that measure runtime and memory for pull requests versus the base branch.
    • Generates comparison reports (including deltas and visualizations), posts/updates a single pinned PR comment, and uploads an interactive plot artifact.
  • Documentation

    • Added benchmark suite documentation covering how to run locally, save/compare baselines, and produce diff reports and memory diagnostics.
  • Chores

    • Introduced dedicated benchmark coverage for aggregation performance across multiple realistic configurations and scaling dimensions.
    • Excluded generated benchmark outputs from version control.

…ting

Benchmark aggregate() under the production config focus (hierarchical,
Distribution scope=global, include_period_sums=False, extremes replace):
a deterministic representation x extremes grid, the representation x
columns interaction, preserve_column_means, and column/slice/day scaling.

The Benchmarks workflow adapts fluxopt's sticky-comment reporting: it
runs the suite on the PR head and base in the same job, posts a benchmem
delta table (walltime + memray peak) as one sticky PR comment, and
uploads an interactive benchmem plot artifact. Informational only, never
blocks a merge. Local baselines land in .benchmarks/ (gitignored,
machine-specific).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@FBumann, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a9c4f5c2-eebf-4f49-bb13-f409daa9a558

📥 Commits

Reviewing files that changed from the base of the PR and between 95b2bd9 and 3bf495f.

📒 Files selected for processing (4)
  • .github/workflows/benchmarks.yaml
  • README.md
  • benchmarks/test_bench_aggregate.py
  • docs/index.md
📝 Walkthrough

Walkthrough

Adds a parameterized aggregate() benchmark suite with deterministic synthetic datasets and a CI workflow that compares pull-request head and base results, publishes reports, uploads plots, and maintains a sticky comment.

Changes

Aggregate benchmarks

Layer / File(s) Summary
Aggregate benchmark suite and scenarios
benchmarks/test_bench_aggregate.py, benchmarks/README.md, .gitignore
Adds synthetic hourly data generation, aggregate configuration variants, representation and extremes grids, preservation checks, scaling benchmarks, local usage documentation, and ignores generated benchmark output.
Head-versus-base CI reporting
.github/workflows/benchmarks.yaml
Runs pinned memory-focused benchmarks for head and base revisions, generates comparison reports and plots, uploads the HTML plot, appends the step summary, and creates or updates a marked pull-request comment.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant pytestBenchmem
  participant BaseRevision
  participant BenchmarkReport
  GitHubActions->>pytestBenchmem: Run aggregate benchmarks for PR head
  GitHubActions->>BaseRevision: Checkout base and overlay benchmarks
  BaseRevision->>pytestBenchmem: Run aggregate benchmarks for base
  pytestBenchmem->>BenchmarkReport: Provide benchmark JSON results
  BenchmarkReport->>GitHubActions: Generate delta table and plot
  GitHubActions->>GitHubActions: Upload plot and update sticky comment
Loading

Possibly related PRs

  • FBumann/tsam_xarray#9: Introduces the stack_dims/slice_dims API exercised by the new aggregate benchmarks.
🚥 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 summarizes the main change: adding a pytest-benchmem benchmark suite with PR head-vs-base reporting.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/benchmarks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 5

🤖 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 @.github/workflows/benchmarks.yaml:
- Around line 77-79: Update the benchmark summary-generation branch in the
workflow to use standalone-run wording when triggered by workflow_dispatch,
rather than claiming a “Δ% vs base” comparison. Preserve the existing
base-comparison wording for runs where base.json is available, and keep the
surrounding runner-noise and artifact notes appropriate to both branches.
- Around line 25-30: Split the benchmark workflow so comparison, summary, and
plot reporting run for every pull request, including forks, without the current
repository-origin guard. Keep only sticky-comment publishing behind the
same-repository check, and place that step in a separate job with pull-requests:
write permissions; preserve continue-on-error for informational benchmark
results.
- Around line 35-37: Update the actions/checkout step in the benchmarks workflow
to set persist-credentials to false, preventing the PR-write token from being
stored in .git/config while preserving the existing fetch-depth setting and
explicit GH_TOKEN usage in later steps.

In `@benchmarks/test_bench_aggregate.py`:
- Around line 156-159: Add a 365-day case to the n_days parameterization of
test_full_scale_days, retaining the existing 730-day case so the benchmark
produces a day-scaling comparison.
- Line 27: Update make_data() to instantiate a new NumPy random generator seeded
with 42 for each dataset creation, and use that local generator instead of the
module-level RNG. Remove or stop using the global RNG so each benchmark case
produces identical data regardless of test selection or execution order.
🪄 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: 6aab7866-59a6-44c9-a28b-89baacdec665

📥 Commits

Reviewing files that changed from the base of the PR and between 6cd9f5d and c146893.

📒 Files selected for processing (4)
  • .github/workflows/benchmarks.yaml
  • .gitignore
  • benchmarks/README.md
  • benchmarks/test_bench_aggregate.py

Comment thread .github/workflows/benchmarks.yaml Outdated
Comment on lines +25 to +30
# Commenting needs a writable token; fork PRs only get a read-only one.
if: >-
${{ github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ubuntu-24.04
continue-on-error: true # informational, never blocks a merge

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Run benchmark reports for fork PRs too.

The job guard skips the comparison, summary, and plot for every fork PR, not just the sticky comment. Split this into a read-only report job for all PRs and a same-repository comment job with pull-requests: write.

Also applies to: 94-108

🤖 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 @.github/workflows/benchmarks.yaml around lines 25 - 30, Split the benchmark
workflow so comparison, summary, and plot reporting run for every pull request,
including forks, without the current repository-origin guard. Keep only
sticky-comment publishing behind the same-repository check, and place that step
in a separate job with pull-requests: write permissions; preserve
continue-on-error for informational benchmark results.

Comment thread .github/workflows/benchmarks.yaml
Comment thread .github/workflows/benchmarks.yaml Outdated
Comment thread benchmarks/test_bench_aggregate.py Outdated
Comment thread benchmarks/test_bench_aggregate.py Outdated
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown

Benchmarks

Δ% vs base, one table sorted by biggest change first
(min walltime and memray peak). Peak memory is
deterministic — any delta there is real; walltime on GitHub
runners is noisy, so only large time deltas are meaningful.
The interactive plot is attached as the benchmark-plot artifact.

Full table

benchmarks/test_bench_aggregate.py

name metric base head
test_preserve_column_means[medoid-on] time 0.04477s +7.1%
peak 7.11 MiB 0.0%
test_representation_x_columns[dist_global-128] time 1.275s +5.3%
peak 104 MiB 0.0%
test_representation_x_columns[medoid-64] time 0.2154s +4.2%
peak 55.3 MiB 0.0%
test_full_scale_slices[4] time 0.3951s +3.7%
peak 8.69 MiB 0.0%
test_full_scale_slices[8] time 0.7866s +3.0%
peak 14.3 MiB 0.0%
test_config_grid[mean-none] time 0.04374s +2.8%
peak 6.59 MiB 0.0%
test_full_scale_columns[128] time 1.262s +2.7%
peak 104 MiB 0.0%
test_config_variants[full] time 0.09985s +2.6%
peak 6.6 MiB 0.0%
test_full_scale_columns[64] time 0.6417s +2.6%
peak 51.2 MiB 0.0%
test_config_variants[dist_global] time 0.09934s +2.5%
peak 6.59 MiB 0.0%
test_config_grid[medoid-append] time 0.04737s +2.5%
peak 7.13 MiB 0.0%
test_config_grid[dist_cluster-append] time 0.04769s +2.3%
peak 6.61 MiB 0.0%
test_config_variants[default] time 0.0466s +2.3%
peak 7.11 MiB 0.0%
test_config_grid[medoid-replace] time 0.04726s +2.2%
peak 7.12 MiB 0.0%
test_config_grid[dist_cluster-replace] time 0.04796s +1.7%
peak 6.6 MiB 0.0%
test_preserve_column_means[medoid-off] time 0.04281s +1.4%
peak 7.09 MiB 0.0%
test_config_grid[mean-append] time 0.04551s +1.4%
peak 6.61 MiB 0.0%
test_representation_x_columns[dist_global-64] time 0.6582s +1.2%
peak 51.2 MiB 0.0%
test_full_scale_days[730] time 0.1262s +1.1%
peak 13.1 MiB 0.0%
test_preserve_column_means[dist_global-on] time 0.09784s +0.9%
peak 6.59 MiB 0.0%
test_config_grid[dist_global-append] time 0.1022s +0.9%
peak 6.61 MiB 0.0%
test_representation_x_columns[medoid-128] time 0.4065s +0.8%
peak 113 MiB 0.0%
test_config_grid[dist_global-none] time 0.09957s +0.7%
peak 6.59 MiB 0.0%
test_preserve_column_means[dist_global-off] time 0.09613s +0.6%
peak 6.57 MiB 0.0%
test_config_grid[dist_global-replace] time 0.1005s +0.2%
peak 6.6 MiB 0.0%
test_config_grid[dist_global_minmax-append] time 0.1019s +0.1%
peak 6.61 MiB 0.0%
test_config_grid[mean-replace] time 0.04559s 0.0%
peak 6.6 MiB 0.0%
test_config_variants[extremes_replace] time 0.04783s 0.0%
peak 7.12 MiB 0.0%
test_config_grid[medoid-none] time 0.04708s -0.2%
peak 7.11 MiB 0.0%
test_full_scale_columns[16] time 0.1748s -0.3%
peak 13 MiB 0.0%
test_config_grid[dist_global_minmax-replace] time 0.1026s -0.6%
peak 6.6 MiB 0.0%
test_config_grid[dist_cluster-none] time 0.04774s -0.7%
peak 6.59 MiB 0.0%
test_representation_x_columns[dist_global-16] time 0.1745s -0.8%
peak 13 MiB 0.0%
test_config_grid[dist_global_minmax-none] time 0.1027s -1.0%
peak 6.59 MiB 0.0%
test_representation_x_columns[medoid-16] time 0.06992s -1.2%
peak 14 MiB 0.0%
test_full_scale_days[365] time 0.1011s -2.9%
peak 6.6 MiB 0.0%

FBumann and others added 3 commits July 23, 2026 21:26
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ruff-action tracks the latest ruff, which now formats Python code
blocks inside markdown and rejects aligned trailing comments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Seed the RNG per make_data call so each case's data is independent of
test selection and execution order. Add a 365-day case so the day axis
scales within its own test. Report one severity-sorted table (group by
module, sort by change). Run fork PRs too — only the sticky comment
needs a writable token, so only that step is repo-guarded — and stop
persisting the checkout token since benchmarks execute PR code. Use
standalone wording when there is no base run to compare against.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@FBumann

FBumann commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #109, which includes these commits plus the suite restructure.

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