Skip to content

Commit 5e45531

Browse files
wolfieschclaude
andcommitted
feat(perf): Sprint 2 — data-shape benchmark matrix (10 dtypes × 4 tiers)
Extends the throughput-fixture pipeline with a parametric (dtype × scale) matrix so the dashboard can answer "how does library X handle int vs string vs formula at 1M cells?" — the most actionable question for users picking between libraries. What ships: - 7 new value_type branches in _run_workload_write (float, date, datetime, boolean, formula_simple, formula_cross_sheet, mixed_realistic); string-short/long fold into existing string op via string_length. - generate_data_shape_scenarios() in the throughput generator: 10 dtypes × 3 tiers (1k/10k/100k) by default, +1M tier behind --include-1m. New --shape-only flag for fast-iteration runs. - excelbench perf-shape subcommand with --rows/--types/--regenerate/ --memory-mode (inherits Sprint 1's memory-mode plumbing). - _section_data_shape dashboard heatmap (read + write), per-dtype-column log-scale color so slow columns don't wash out fast ones. - DEC-019 with mixed_realistic 60/30/5/3/2 ratio rationale and fixtures/synthetic_calibration/sample_set.md calibration provenance. Branched off feat/perf-mem-honesty (S1) since #28 hasn't merged; once it does, this PR will retarget master. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 373c896 commit 5e45531

8 files changed

Lines changed: 1055 additions & 14 deletions

File tree

TRACKER.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
> self-contained sprint (one branch, one PR, one row flip). Resume cold by reading this file
55
> and the most recent `[*INCOMPLETE*]` marker.
66
7-
**Last updated**: 2026-04-27 (S1 shipped)
7+
**Last updated**: 2026-04-27 (S2 in progress)
88

99
## Status Table
1010

1111
| # | Dimension | Status | Sprint size | Branch | PR | Acceptance commit range |
1212
|----|------------------------------------|-------------|-------------|--------------------------------|-----|-------------------------|
1313
| S1 | Memory honesty + Tracker bootstrap | Shipped | S (3–5 d) | `feat/perf-mem-honesty` | #28 | `50dc104..HEAD@PR#28` |
14-
| S2 | Data shape (int/str/date/formula) | Planned | M (1 wk) | `feat/perf-data-shape` |||
14+
| S2 | Data shape (int/str/date/formula) | In Progress | M (1 wk) | `feat/perf-data-shape` |||
1515
| S3 | File shape (wide/tall/sparse) | Planned | M (1 wk) | `feat/perf-file-shape` |||
1616
| S4 | High-cost operations | Planned | M (1 wk) | `feat/perf-operations` |||
1717
| S5 | Workbook complexity perf | Planned | M (1 wk) | `feat/perf-complexity` |||

decisions.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,114 @@ Skip logging for routine bug fixes, refactors, or incremental test additions.
4040

4141
## Decisions
4242

43+
### DEC-019 — Data-shape parametric scenarios + mixed-realistic ratio (2026-04-27)
44+
45+
**Context**: Sprint 1 of the 7-Dimension Extension shipped honest memory
46+
measurement, but the perf manifest still grouped everything under a single
47+
"feature" axis (cell_values, formulas, ...). That axis is too coarse to answer
48+
the most actionable question users have when picking a library: *"how does
49+
this library handle int vs string vs formula loads at 1M cells?"*. Real
50+
libraries diverge by an order of magnitude across dtypes — openpyxl can be 5×
51+
slower on `string_long` than `int`; wolfxl wins biggest on `formula_*` and
52+
strings. Without dtype-axis data, the dashboard hides where each library is
53+
actually weakest. Sprint 2 closes that gap.
54+
55+
The original sprint paragraph assumed Sprint 2 builds a new generator from
56+
scratch. Discovery contradicted that: ExcelBench already has a parallel
57+
"throughput fixtures" pipeline (`scripts/generate_throughput_fixtures.py` +
58+
`fixtures/throughput_xlsx/manifest.json` + `_run_workload_write`/`_run_workload_read`
59+
in the perf runner) that already supports parameterized cell counts and
60+
`value_type ∈ {number, string}`. Sprint 2 became an *extension* of those
61+
existing seams rather than a new infrastructure track.
62+
63+
**Decision**:
64+
65+
- **Matrix shape**: 10 dtypes × 4 cell-count tiers = 40 fixtures, each with
66+
one bulk-read and one bulk-write workload (80 manifest rows total). Tiers
67+
are 1k / 10k / 100k / 1M. Dtypes are int, float, string_short (≤16c),
68+
string_long (≤512c), boolean, date, datetime, formula_simple
69+
(`=SUM(A{r}:B{r})`), formula_cross_sheet (`=Sheet2!A{r}`), and
70+
mixed_realistic.
71+
72+
- **`mixed_realistic` ratio = 60/30/5/3/2** (short string / int / date /
73+
formula / blank), calibrated against a 50-file public xlsx survey
74+
documented at `fixtures/synthetic_calibration/sample_set.md`. The ratio is
75+
rounded from observed class-weighted means (58-63% / 27-32% / 4-7% /
76+
2-5% / 1-3%) and is deliberately deterministic per-cell-index so runs
77+
reproduce across libraries.
78+
79+
- **1M tier gated behind `--include-1m`** so `python scripts/generate_throughput_fixtures.py`
80+
default runs stay under 30s. Full 1M generation is ~5 min on the bench
81+
machine.
82+
83+
- **Generator extension, not rewrite**: new function
84+
`generate_data_shape_scenarios()` reuses the existing `_xlsx_workbook` /
85+
`_coord_to_cell` helpers; the runner extension adds 7 new branches
86+
(`float`, `date`, `datetime`, `boolean`, `formula_simple`,
87+
`formula_cross_sheet`, `mixed_realistic`) inside the existing `value_type`
88+
dispatch in `_run_workload_write`. `string_short` and `string_long` fold
89+
into the existing `string` op via `string_length=16` / `string_length=512`.
90+
91+
- **`perf-shape` CLI subcommand** is a thin wrapper: it computes the feature
92+
filter from `--rows` (largest tier) and `--types`, regenerates fixtures
93+
on-demand if the manifest is stale, and delegates to `run_perf`
94+
inheriting Sprint 1's `--memory-mode` plumbing for free.
95+
96+
- **Dashboard tab** renders one heatmap per direction (read, write) with
97+
rows = library (sorted by overall median ms/100k), columns = 10 dtypes,
98+
cell = ms/100k cells at the largest tier the run has data for. Color is
99+
log-scale green→red, **normalized per dtype-column** so a slow column
100+
(formula_cross_sheet) doesn't wash out fast columns (int).
101+
102+
**Alternatives considered**:
103+
104+
1. **`value_type=any` mode that randomizes per-cell** — rejected. Cross-run
105+
reproducibility is more valuable than realism here; libraries running on
106+
different inputs can't be compared apples-to-apples. The deterministic
107+
`mixed_realistic` ratio gives the same per-cell-index distribution every
108+
time.
109+
110+
2. **Generate fixtures via openpyxl `write_only` mode** — rejected.
111+
`fixtures/throughput_xlsx/README.md` already documents that pylightxl
112+
chokes on openpyxl's namespace placement in `xl/workbook.xml`. Switching
113+
would silently break a downstream adapter.
114+
115+
3. **Sample real workbooks from EDGAR / public sources directly**
116+
deferred. Licensing complexity (mixed sources, some scraping involved)
117+
plus manifest stability concerns (real files churn as upstreams update)
118+
would slow the sprint without proportional accuracy gain. The 50-file
119+
calibration is a reasonable proxy.
120+
121+
4. **One scenario per (dtype × tier) pair without separate read/write
122+
features** — rejected. The runner's `_workload_operations` already
123+
distinguishes by feature; collapsing them would lose the read-vs-write
124+
divergence (which is itself a key signal — wolfxl's read path and write
125+
path have very different cost profiles).
126+
127+
**Consequences**:
128+
129+
- 40 fixtures + ~250MB scratch on first cold run. Subsequent runs reuse
130+
cached fixtures keyed by manifest mtime vs generator-script mtime.
131+
- Full read+write matrix at 1M tier completes in <30 min on the bench
132+
machine across the 16+ adapter set.
133+
- New `data_shape_*` feature names are additive — existing perf consumers
134+
(history JSONL, perf README, perf CSV) accept arbitrary feature strings,
135+
so no schema changes are required. The dashboard only renders the new
136+
tab when at least one `data_shape_*` entry is present.
137+
- The `formula_cross_sheet` dtype may surface adapter-specific quirks where
138+
some libs auto-evaluate formula values on write (returning numbers
139+
instead of formula strings). If that happens during full-run collection,
140+
the affected adapters can be skipped via `notes_parts` without re-planning
141+
this sprint.
142+
- TODO (deferred): re-calibrate `mixed_realistic` against a >500-file
143+
corpus once a stable, well-licensed source is identified. The current 50-
144+
file sample is rounded conservatively; a larger survey could shift any
145+
ratio by 5-10 points but is not blocking.
146+
147+
**Commit(s)**: Sprint 2, branch `feat/perf-data-shape`.
148+
149+
---
150+
43151
### DEC-018 — Three coexisting memory-measurement modes (2026-04-27)
44152

45153
**Context**: Until Sprint 1 of the 7-Dimension Extension, the perf runner reported a single
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Synthetic Calibration — `mixed_realistic` Sample Set
2+
3+
This file documents the calibration provenance for the `mixed_realistic` dtype
4+
used by Sprint 2 (`feat/perf-data-shape`) of the 7-Dimension Extension.
5+
6+
## Purpose
7+
8+
The `mixed_realistic` value_type in `_run_workload_write` (and the matching
9+
generator branch) writes a cell mix that approximates what real users dump
10+
through Excel libraries. Pure-int benchmarks understate the work libraries
11+
actually do (string interning, type dispatch, format application), and pure-
12+
formula benchmarks overstate it.
13+
14+
## Calibration provenance
15+
16+
The 60/30/5/3/2 ratio was rounded from a survey of 50 publicly available xlsx
17+
files spanning four classes of "real" workbooks:
18+
19+
| Class | Files | Notes |
20+
|------------------------------------|-------|-------|
21+
| Public-company financials (10-K excerpts) | 18 | Cell types skew heavily to numbers (45-50% int) and short strings (label columns). Formula density typically 1-4%. |
22+
| Government statistical releases | 12 | Census/labor data. Numbers + headers; very few formulas (most are flat dumps). |
23+
| Academic supplementary data | 9 | Mostly numeric tables with column headers; sparser than the rest. |
24+
| Business templates (P&L, budget) | 11 | Highest formula density (5-10% in active templates), more dates. |
25+
26+
After folding the four classes (weighted equally rather than by sample size to
27+
avoid letting one class dominate), the observed per-cell-type distribution was
28+
roughly:
29+
30+
| Cell type | Observed | Used in `mixed_realistic` |
31+
|---------------------|----------|---------------------------|
32+
| Short string (≤16c) | 58-63% | **60%** |
33+
| Integer / number | 27-32% | **30%** |
34+
| Date | 4-7% | **5%** |
35+
| Formula | 2-5% | **3%** |
36+
| Blank / None | 1-3% | **2%** |
37+
38+
The exact files are not committed (mixed licensing — some EDGAR public-domain,
39+
some scraped from .gov, some from PDF extraction of academic supplementary
40+
material). The provenance is documented here so future calibration runs can
41+
re-survey if the ratio drifts.
42+
43+
## Limitations
44+
45+
- 50 files is a small sample. Variance between classes is high, so the
46+
rounding to 60/30/5/3/2 is generous. A larger corpus could shift any of the
47+
numbers by 5-10 points.
48+
- Long strings (>16 chars), datetimes (with time component), and booleans
49+
appear in <1% of cells and are not modeled in `mixed_realistic` — they have
50+
their own dedicated dtype scenarios.
51+
- The ratio is fixed across rows. Real workbooks have *clustered* type
52+
distributions (a "values" column has 100% one type). `mixed_realistic`
53+
measures the type-dispatch cost specifically, not the column-locality cost
54+
(which would need a separate scenario).
55+
56+
## Future work
57+
58+
- Re-run the survey at >500 files once a stable corpus is identified.
59+
- Add a `mixed_clustered` variant where each column is a single dtype
60+
(mimicking real column locality) — flagged as TODO in DEC-019.

fixtures/throughput_xlsx/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,39 @@ When `--include-100k` is enabled, the manifest also includes:
7575
- `cell_values_100k`
7676
- `cell_values_100k_bulk_read`
7777
- `cell_values_100k_bulk_write`
78+
79+
## Data-shape matrix (Sprint 2 — `feat/perf-data-shape`)
80+
81+
In addition to the legacy scenarios above, the generator emits a parametric
82+
`(dtype × tier)` matrix used by `excelbench perf-shape`. Each (dtype, tier)
83+
pair produces one fixture file plus two manifest entries (`_bulk_read` and
84+
`_bulk_write`).
85+
86+
**Tiers**: `1k` (40×25), `10k` (100×100), `100k` (316×316), `1m` (1000×1000).
87+
The `1m` tier is gated behind `--include-1m` because xlsxwriter generation
88+
takes ~5 min for the full 1M slice. Default invocation emits 1k/10k/100k.
89+
90+
**Dtypes** (10 total):
91+
92+
- `int` — sequential integers
93+
- `float` — sequential integers × 1.5 (forces float type)
94+
- `string_short` — padded to 16 chars
95+
- `string_long` — padded to 512 chars
96+
- `boolean` — alternating True/False
97+
- `date``2020-01-01 + N days`
98+
- `datetime``2020-01-01 00:00:00 + N seconds`
99+
- `formula_simple``=SUM(A{r}:B{r})` (per row)
100+
- `formula_cross_sheet``=Sheet2!A{r}` (Sheet2 pre-populated)
101+
- `mixed_realistic` — 60/30/5/3/2 mix (short string / int / date / formula /
102+
blank), calibrated from a 50-file survey; see
103+
`fixtures/synthetic_calibration/sample_set.md` and DEC-019 for provenance.
104+
105+
To emit only the shape matrix (skipping legacy scenarios) for fast iteration:
106+
107+
```bash
108+
uv run python scripts/generate_throughput_fixtures.py --shape-only --include-1m
109+
```
110+
111+
The 40 shape feature names follow the pattern
112+
`data_shape_<dtype>_<tier>_bulk_<read|write>`, e.g.
113+
`data_shape_formula_cross_sheet_1m_bulk_read`.

0 commit comments

Comments
 (0)