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
{{ message }}
Repository navigation
Commit bc3d64b
Browse filesBrowse the repository at this point in the historyBrowse files
perf(verification): stop paying OpenBLAS for arithmetic nobody does (FB-17)
The Windows shard suite sat 5.3% under the host's 1 GiB per-process guard,
and the audit read that as a few heavy test modules to split up. Measuring
says otherwise: the peak was a flat per-process cost every shard paid once.
import numpy 655.6 MiB
import numpy, OPENBLAS_NUM_THREADS=1 109.5 MiB
INSERT ... VALUES ('r', 't', 200) 86.0 MiB
the same insert as VALUES (?, ?, ?) 703.1 MiB
OpenBLAS commits per-core scratch buffers when it loads -- ~30 MiB x 18
cores -- and duckdb reaches numpy on its own, importing pandas/pyarrow to
convert bound parameters. That is why every module writing to a DuckDB
store looked heavy and the docs-ratchet modules the audit suspected did
not. Splitting shards could never have helped: the cost does not divide.
The runner now forces OPENBLAS_NUM_THREADS=1 and OMP_NUM_THREADS=1 into
collection and every shard. Forced rather than defaulted, because a stray
OMP_NUM_THREADS=8 in an operator's shell would put the cost back silently.
No file under src/ and no test imports numpy, so nothing loses arithmetic.
The budget drops 1000 -> 600 MiB, which is a ratchet again instead of a
near-miss of the guard, and the docstring stops quoting a "default 900 MiB"
that had outlived two changes. The failure message no longer says "split it
further" -- provably wrong advice -- and names the per-file attribution
command instead.
The API image does not pay any of this: requirements-docker.lock ships
neither numpy nor pandas, and the same parameterized insert with both made
unimportable writes its row at 87.6 MiB. docs/operations/windows-verification.md
records the measurements and a test fails if that lock ever gains them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XmsELVj792npHTA1m13mEM
Copy file name to clipboardExpand all lines: AGENTS.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,5 +10,6 @@
10
10
## Verification Hosts
11
11
- Keep local Windows Python processes below 1 GiB. Use narrow checks and rely on `D:\SystemState\PythonMemoryGuard\python-memory-guard.ps1`.
12
12
- Run Docker-heavy verification, jobs likely to exceed 1 GiB, and macOS diagnostics through SSH alias `deproject-mac`; the Mac checkout is `/Users/julia/agentflow-docker-check`.
13
+
- The sharded Windows suite pins `OPENBLAS_NUM_THREADS=1` and `OMP_NUM_THREADS=1` in every child process; unpinned, OpenBLAS alone accounts for ~550 MiB of the 1 GiB budget. Measurements and the production caveat: `docs/operations/windows-verification.md`.
13
14
- Non-secret Mac context belongs in `AGENT_STATE.md`, `docs/SESSION_HANDOFF.md`, and `docs/operations/local-verification-matrix.md`.
14
15
- Never print, commit, or copy SSH keys, passwords, tokens, recovery codes, or other secrets into project files, logs, PRs, or chat.
Copy file name to clipboardExpand all lines: docs/operations/README.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -44,6 +44,7 @@ are inputs to later work, not general-purpose procedures.
44
44
|[API DuckDB persistence and recovery design](api-duckdb-persistence-recovery-design.md)| Current preservation/recovery authorization-boundary owner | Status is `CAPABILITY_REHEARSAL_REQUIRED`; it is not an approved operator runbook |
45
45
|[CI-soak Compose foundation](ci-soak-compose-foundation.md)| Topology and historical implementation reference | Its runtime-status sequence is superseded; resume from the current CI-soak runbook above |
46
46
|[OpenSSF security posture](openssf-security-posture.md)| Scope and interpretation of free supply-chain posture signals | Neither Scorecard nor self-certification is a penetration test or attestation |
47
+
|[Windows verification memory](windows-verification.md)| What the sharded Windows suite's per-process budget is spent on, measured | Describes the test host; pinning the same variables in the API image is an owner decision that page does not take |
The Windows verification host kills any Python process that passes 1 GiB, which
4
+
is why `tests/unit` runs as sequential shards instead of one pytest command
5
+
(audit F-07). This page owns what that budget is actually spent on. It exists
6
+
because the budget was raised against the wrong cause, and the next person to
7
+
touch it should start from the measurement rather than from the shard size.
8
+
9
+
**Audience:** maintainers running or tuning the Windows no-Docker verification suite
10
+
11
+
**Prerequisites:** a local checkout with the project virtualenv installed; the numbers below were measured on the supported 18-core Windows host on 2026-09-07, except where a line attributes them otherwise, with duckdb 1.5.4, numpy 2.3.5, pandas 3.0.3 and pyarrow 24.0.0
12
+
13
+
## Where the memory goes
14
+
15
+
Nearly all of it is one flat, per-process cost, paid the first time anything in
16
+
the process imports numpy:
17
+
18
+
| Measured with `scripts/run_windows_unit_shards.py <file>`| Peak |
19
+
| --- | --- |
20
+
|`import numpy`| 655.6 MiB |
21
+
|`import numpy`, with `OPENBLAS_NUM_THREADS=1`| 109.5 MiB |
22
+
|`import numpy, pandas, pyarrow`, with the thread variables pinned | 141.0 MiB |
23
+
|`INSERT INTO t VALUES ('r', 't', 200)` on a DuckDB file | 86.0 MiB |
24
+
| the same insert as `VALUES (?, ?, ?)` with bound parameters | 703.1 MiB |
25
+
26
+
Two mechanisms combine:
27
+
28
+
-**OpenBLAS commits per-core scratch buffers when it loads.** On this host that
29
+
is roughly 30 MiB times 18 cores, and it is charged to the process whether or
30
+
not any array arithmetic ever happens. Pinning the pools to a single thread
31
+
removes it.
32
+
-**DuckDB reaches numpy on its own.** Passing parameters to `execute()` makes
33
+
the Python client import pandas and pyarrow — and therefore numpy — to convert
34
+
the bound values. The same statement written with literals never loads them.
35
+
Parameter binding is the correct way to write these queries, so this is a cost
36
+
to bound, not to avoid.
37
+
38
+
That second point is why the heavy modules looked like a random assortment:
39
+
every test that writes to a DuckDB store paid it, and nothing else did. Bisecting
40
+
one 797 MiB test in `tests/unit/test_analytics_middleware.py` reached
41
+
`ensure_analytics_table` at 91.6 MiB and the single session insert after it at
42
+
810.8 MiB, which is the whole finding in one line.
43
+
44
+
## Why a smaller `--shard-size` never helped
45
+
46
+
The cost is per process and flat, so it does not divide. An earlier pass on the
47
+
same host (2026-08-21) recorded a 14-file shard at 830 MiB next to a 147-test
48
+
shard at 94 MiB; splitting either one changes nothing, because each resulting
49
+
process still imports numpy once. Those numbers were read as "individual heavy
50
+
test modules" and the budget was raised instead, which is how the suite arrived
51
+
at a 969.8 MiB worst shard against a 1024 MiB guard — 5.3% of headroom.
52
+
53
+
## What the runner does now
54
+
55
+
`scripts/run_windows_unit_shards.py` forces `OPENBLAS_NUM_THREADS=1` and
56
+
`OMP_NUM_THREADS=1` into every child process it starts, for collection as well
57
+
as for each shard. The variables are forced rather than defaulted: an
58
+
`OMP_NUM_THREADS=8` left over in an operator's shell would otherwise put the
59
+
whole cost back without any visible sign.
60
+
61
+
Neither `src/` nor the unit suite computes with numpy — it is loaded only as
62
+
DuckDB's conversion helper — so single-threaded BLAS has nothing to slow down.
0 commit comments