Skip to content

Commit 090e021

Browse files
committed
less text
1 parent 049b7e3 commit 090e021

21 files changed

Lines changed: 337 additions & 803 deletions

.github/workflows/codspeed.yml

Lines changed: 23 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,18 @@
1-
# Perf-regression benchmarks: instruction-count (Callgrind) gating against a COMMITTED baseline.
1+
# Instruction-count (Callgrind) perf-regression gate against a COMMITTED baseline. No CodSpeed account/token/runner:
2+
# compare_baseline.py parses raw callgrind dumps and diffs each benchmark against benchmarks/baseline.json. Counts
3+
# are near-deterministic with PYTHONHASHSEED pinned (~0.1% noise), so the 5% gate threshold sits far above it.
4+
# Details + rationale: benchmarks/README.md and benchmarks/PLAN.md.
25
#
3-
# NO CodSpeed account/dashboard/token/runner. pytest-codspeed's hooks call callgrind_dump_stats_at(<uri>) per
4-
# benchmark, so a self-hosted `valgrind --tool=callgrind` run writes one dump per benchmark, headed by
5-
# `desc: Trigger: Client Request: <uri>` with the count on `totals:` (events: Ir). benchmarks/compare_baseline.py
6-
# parses those dumps and diffs each benchmark against benchmarks/baseline.json (the committed instruction-count
7-
# baseline). Counts are near-deterministic under Callgrind with PYTHONHASHSEED pinned (~0.1% noise observed;
8-
# often bit-identical), so a 5% default gate threshold sits far above noise. Validated on a Linux+valgrind box.
6+
# Triggers: nightly schedule + manual workflow_dispatch (no pull_request/push). A dispatch on a feature branch
7+
# compares that branch's counts vs the baseline.json committed on it, answering "did my branch regress vs main".
98
#
10-
# TRIGGERS: nightly `schedule` + manual `workflow_dispatch`. No pull_request/push (and no `paths:` -- neither
11-
# schedule nor dispatch honors it). A dispatch on a feature branch compares that branch's benchmark counts vs the
12-
# baseline.json committed on the branch (i.e. main's baseline), answering "did my branch regress vs main".
9+
# Modes (workflow_dispatch input `regen`):
10+
# regen=false (default) -> COMPARE + report. Report-only for now (never fails); flip to --enforce once trusted.
11+
# regen=true -> write a fresh baseline.json + upload as an artifact to commit deliberately. Bump
12+
# requirements-bench.txt FIRST (separate commit) if the pins should change.
1313
#
14-
# MODES (workflow_dispatch input `regen`):
15-
# regen=false (default) -> COMPARE: run + diff vs baseline.json, print a report. REPORT-ONLY for now (never
16-
# fails the job); flip compare_baseline.py to --enforce once trusted.
17-
# regen=true -> REGENERATE: run + write a fresh baseline.json (per-bench counts + provenance meta +
18-
# Option-B binding fractions/auto-move) and upload it as an artifact to commit
19-
# deliberately. Bump benchmarks/requirements-bench.txt in a separate commit FIRST if
20-
# the pins should change, then regen so the baseline matches the committed pins.
21-
#
22-
# The concurrency module is EXCLUDED from the Callgrind sweep: Callgrind serializes threads, so its signal
23-
# (wall-clock GIL contention) is meaningless here; it stays a local walltime tool.
24-
#
25-
# MEMORY MODE (a second Callgrind sweep for O(rows) produce peak-RSS) is DESIGNED but DEFERRED -- see PLAN.md.
26-
#
27-
# Valgrind is slow (~20-50x); timeout-minutes is a conservative guess -- calibrate after the first CI run.
14+
# The concurrency module is excluded from the sweep (Callgrind serializes threads, so its signal is meaningless).
15+
# Memory mode (a second sweep for produce peak-RSS) is deferred (see PLAN.md).
2816

2917
name: Benchmarks
3018

@@ -45,17 +33,14 @@ concurrency:
4533
jobs:
4634
benchmarks:
4735
runs-on: ubuntu-latest
48-
timeout-minutes: 90 # measured: ~25 min Callgrind sweep at BENCH_SCALE=10 (12-core Linux) + cold build ~10 min; margin for CI
36+
timeout-minutes: 90 # ~25 min sweep at BENCH_SCALE=10 (12-core Linux) + ~10 min cold build; margin for CI
4937
permissions:
5038
contents: read
5139
env:
52-
PYTHONHASHSEED: "0" # pin hash randomization so dict/struct paths give stable instruction counts (INFRA-6)
53-
CODSPEED_ENV: "1" # activates pytest-codspeed's instrument hooks (the callgrind_dump_stats_at markers)
54-
# env-gated row counts (INFRA-4): shrink the O(rows)/per-row-object benchmarks so the Callgrind sweep fits
55-
# under timeout-minutes. Local runs leave this unset -> full N. Recorded in baseline.json meta.bench_scale;
56-
# a baseline is only comparable to a run at the SAME scale. Calibrated on a 12-core Linux+valgrind box:
57-
# BENCH_SCALE=10 -> ~25 min full sweep, and the Option-B move-list matches full-N (fractions shift slightly
58-
# but stay the same side of the cutoff). Most benches floor at 20k rows (_scale.FLOOR), still row-dominated.
40+
PYTHONHASHSEED: "0" # stable instruction counts for dict/struct paths
41+
CODSPEED_ENV: "1" # activates pytest-codspeed's instrument hooks
42+
# shrink the O(rows) benches so the sweep fits under timeout-minutes. Local runs leave this unset -> full N.
43+
# Recorded in baseline.json meta.bench_scale; a baseline only compares to a run at the SAME scale.
5944
BENCH_SCALE: "10"
6045
steps:
6146
- uses: actions/checkout@v4
@@ -95,22 +80,19 @@ jobs:
9580
run: |
9681
# step 1: build deps only (needed for --no-build-isolation), no project
9782
uv sync --only-group build --no-install-project -p 3.13
98-
# step 2: build+install the project (release) + build group, WITHOUT the heavy default `dev` group
99-
# (torch/tensorflow/pyspark). uv.lock is gitignored, so it is deliberately NOT relied on for bench deps.
83+
# step 2: build+install the project (release) + build group, without the heavy default `dev` group
10084
uv sync --no-build-isolation --no-editable --reinstall --no-default-groups --group build -p 3.13
101-
# step 3: install the FROZEN, committed bench pins (exact ==). Regenerated deliberately with the baseline
102-
# (source list: pyproject [dependency-groups] bench), so the only cross-run delta is the binding.
85+
# step 3: the frozen bench pins (exact ==), so the only cross-run delta is the binding
10386
uv pip install -r benchmarks/requirements-bench.txt
10487
10588
- name: Collect gate node-ids
106-
# the gate/informational split (conftest markers) classifies which benchmarks are gate-able; regen uses it
89+
# the gate/informational marker split; regen uses it to classify each benchmark
10790
run: uv run --no-sync pytest benchmarks/ -m gate --collect-only -q -o addopts= -p no:cacheprovider \
10891
| grep '::' > gate_list.txt || true
10992

11093
- name: Run benchmarks under Callgrind (per-benchmark instruction counts)
111-
# ONE sweep over all gate+informational benchmarks EXCEPT the concurrency module (Callgrind serializes
112-
# threads -> its wall-clock signal is meaningless and it is expensive). Each benchmark emits a callgrind
113-
# dump keyed by its uri. The pytest-codspeed hooks obj-skip libpython, so counts are clean.
94+
# ONE sweep over gate+informational EXCEPT the concurrency module (thread-serialized, expensive). Each
95+
# benchmark emits a callgrind dump keyed by its uri.
11496
run: |
11597
mkdir -p profiles
11698
CODSPEED_PROFILE_FOLDER="$PWD/profiles" valgrind --tool=callgrind --instr-atstart=no \
@@ -121,8 +103,7 @@ jobs:
121103
122104
- name: Compare against committed baseline (report-only)
123105
if: ${{ !inputs.regen }}
124-
# report-only for now: prints the per-benchmark delta table and NEVER fails the job. Add --enforce here
125-
# once trusted to fail on a gate regression (informational benches never fail).
106+
# report-only: prints the delta table, never fails the job. Add --enforce once trusted.
126107
run: |
127108
uv run --no-sync python benchmarks/compare_baseline.py compare \
128109
--profiles profiles --baseline benchmarks/baseline.json \

0 commit comments

Comments
 (0)