You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: benchmarks/PLAN.md
+49-11Lines changed: 49 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -122,20 +122,58 @@ benchmarks/
122
122
```
123
123
One module per binding subsystem so a CodSpeed report points at one src/ area. torch/tf go in produce_numpy (wrap FetchNumpyInternal); polars stays in arrow (wraps FetchArrowTable).
124
124
125
+
> **Note (reconciled to the implemented model).** The prose below originally described a per-PR CodSpeed
126
+
> commit-diff gate. That is NOT how the suite works now. The implemented model is: **nightly `schedule` +
127
+
> manual `workflow_dispatch`** (no per-PR trigger, no CodSpeed account/token/runner), a **self-hosted
128
+
> `valgrind --tool=callgrind`** sweep that emits one dump per benchmark, and **`compare_baseline.py`** diffing
129
+
> those counts against a **committed `benchmarks/baseline.json`**. See `.github/workflows/codspeed.yml`.
130
+
125
131
### Walltime vs instruction-count
126
132
127
133
-**Local A/B (macOS arm64): walltime only** (no Valgrind), `--codspeed-mode=walltime`.
Instruction-count is ideal AND should gate the GIL-held single-threaded overhead paths: fetchone loop, fetchall/fetchmany, native UDF per-call, native values() ingest, analyzer bind, all per-element converters (FromValue, TransformPythonValue, NumpyScan object/string, ArrayWrapper fill). The historical fetchall regression would be caught cleanly here.
131
-
132
-
Noisy under instruction-count — keep walltime-only, informational, do NOT hard-gate:
133
-
- to_arrow_table / pl() on materialized results: PromoteMaterializedToArrow re-runs the query parallel with GIL released (`pyresult.cpp:450-477`).
Gate tactic: pair each large-throughput scenario with a small/1-row variant (e.g. fetchall range(1_000_000) walltime + fetchall range(2048) instruction-count gate) so binding fixed-cost is measured noise-free.
134
+
-**CI: instruction-count via self-hosted Callgrind (Linux)**, near-deterministic (~0.1% noise with
135
+
`PYTHONHASHSEED=0`; often bit-identical) — compared against the committed baseline, **report-only** for now
136
+
(flip `compare_baseline.py` to `--enforce` when trusted).
### New coverage dimensions (beyond the converter surface)
166
+
167
+
-**Concurrency/GIL** (`test_concurrency_perf.py`, informational/walltime): threads {1,4,8} over a **multi-batch**
168
+
arrow scan / pandas scan / native + arrow UDF. EXCLUDED from the Callgrind sweep (Callgrind serializes threads
169
+
→ its wall-clock contention signal is meaningless there); it is a local walltime tool.
170
+
-**Sustained-leak guard** (`tests/fast/test_binding_pressure_leak.py`): a plain psutil RSS + object-count
171
+
ratio test (not a codspeed benchmark) for the object-pinning paths (register/unregister, UDF create/run/remove,
172
+
executemany). Runs in the normal test suite.
173
+
-**Memory mode (DEFERRED).** A second Callgrind sweep (`--codspeed-mode=memory`) over the O(rows) produce paths
174
+
for peak-RSS, feeding the same baseline model, is DESIGNED but not implemented this round (roughly doubles the
175
+
CI cost; nightly-only when added). The `test_mem_df_with_nulls` tracemalloc guard stays as a local signal until
176
+
then (convert it to an A/B delta when memory mode lands).
139
177
140
178
### Two code-grounded gotchas
141
179
-**OUT-col null benchmarks need REAL DuckDB nulls** (`CASE WHEN ... THEN NULL`): the masked-array branch only triggers on an actually-invalid validity bit (`array_wrapper.cpp:396-404,736`); a no-null column silently takes the cheap `std::move` path and measures the wrong thing.
0 commit comments