Support for abi3 and forthcoming abi3t - #697
Conversation
Set wheel.py-api = "cp311" so scikit-build-core tags the wheel cp311-abi3, and turn the SKBUILD_SABI_* variables it exports into USE_SABI on each extension target. USE_SABI defines Py_LIMITED_API, which is what flips Cython into CYTHON_LIMITED_API mode, and together with WITH_SOABI names the modules <name>.abi3.so. A plain cmake invocation sets neither variable, so the version-specific behaviour is unchanged outside of a wheel build. No .pyx changes were needed. The cdef classes (SChunk, vlmeta) build as PyType_FromSpec heap types, the legacy `np.ndarray[dtype, ndim=N]` buffer syntax in indexing_ext still compiles, and utf8_ext's NpyString usage is limited-API clean, all with Cython 3.2.9 and NumPy 2.5. Measured on macOS arm64, 4 interleaved runs, min-of-7 per measurement: worst case 1.013x (compress2 of 8 KB buffers, i.e. call-overhead bound), everything else within noise. SChunk attribute access -- the most direct probe of the heap-type path -- came out at 0.987x. Full suite passes on the build interpreter (3.14) and, using that same wheel, on 3.11/3.12/3.13. Free-threaded builds keep their own version-specific wheels: scikit-build-core ignores py-api when Py_GIL_DISABLED is set, so cp314t/cp315t need no extra configuration. Collapsing those into one wheel needs abi3t (PEP 803), which starts at 3.15 and requires a PEP 793 module export hook that Cython cannot emit yet. So this is 7 wheels per platform down to 3, not 2. Since the abi3 wheel is built only on 3.11 and cibuildwheel tests a wheel just on the interpreter that produced it, add test_abi3_matrix: it installs the single wheel on 3.11-3.15 across all three OSes, asserts the .abi3. module is the one loaded, and runs a slice of the suite. That is also the job that would catch a future CPython breaking the stable ABI -- the risk abi3 takes on in exchange for not rebuilding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Windows legs of test_abi3_matrix failed on all five Python versions while the wheel builds and the Linux/macOS legs passed. The wheel was fine: the install step succeeded and only the assertion step failed. ".abi3." is a POSIX naming convention. On Windows a limited-API extension is just `blosc2_ext.pyd`, against `blosc2_ext.cp314-win_amd64.pyd` for a version-specific build -- there is no abi3 infix to look for. Check the portable invariant instead: importlib's EXTENSION_SUFFIXES[0] is the version-tagged suffix on every platform, so an abi3 module is exactly the one whose filename does not end with it. Verified locally against both a normal and an abi3 build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The benchmarking behind the abi3 switch covered exactly one cell: macOS arm64, CPython 3.14, Apple clang. That is thin evidence for a change whose whole mechanism is platform-dependent. The generated C is byte-identical between the two builds -- CYTHON_LIMITED_API is a preprocessor macro, so the divergence happens entirely in the C compiler. It is not a small divergence: the abi3 objects are 3-10% smaller (blosc2_ext -10.2%) because Cython's inlined fast paths become calls into libpython. So abi3 moves work onto libpython calls, and the cost of such a call is not the same everywhere. Windows is the cell that matters. There an abi3 extension links python3.dll, a forwarder DLL, so each of those newly-added calls takes an extra thunk into python3XY.dll. POSIX has no equivalent -- symbols resolve straight from the loaded interpreter. 3.11 and 3.14 are both built because one abi3 binary serves the whole range while the interpreter-side handling differs by version. Rounds are interleaved abi3/base/abi3/... so a slow patch on a shared runner hits both builds instead of biasing whichever ran first, and the threshold is a deliberately loose 1.25x: this environment can resolve an extra indirection on every call, not a 3% difference. Benchmarks under 5 ms are reported but never fail the job. The report is posted as a PR comment because job logs and step summaries are not readable through the public API without admin rights. Also extends the benchmark to the paths the local run skipped: utf8 ingest, group_by+agg, multi-key group_by and where() over a 200k-row CTable, which is what actually exercises utf8_ext, groupby_ext and indexing_ext. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ssion"
All six bench cells failed at "Create both environments" while every build
step, Windows included, succeeded. The cause was
PY=$(ls venv-$v/bin/python venv-$v/Scripts/python.exe 2>/dev/null | head -1)
GitHub runs `shell: bash` as `bash --noprofile --norc -eo pipefail`, so `set -e`
is active, and an assignment takes the exit status of its command substitution.
One of those two paths is always absent -- that is the whole point of listing
both -- so ls exits 2 and kills the step. Replaced with an explicit -x test at
both call sites.
Also stop conflating outcomes in compare.py. It exited 1 both when a benchmark
regressed and when no artifacts existed at all, so this run reported a
performance regression when what actually happened was that no benchmark ever
ran. It now always exits 0 and publishes status=ok|regressed|nodata through
GITHUB_OUTPUT, with the workflow failing on each for its own reason and its own
message. The report file is written unconditionally so the PR-comment step has
something to post either way.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
abi3 vs. version-specific buildMinimum of interleaved rounds. Regression threshold 1.25x (benchmarks under 5 ms are reported but never fail the job -- CI runners cannot resolve them). macos-latest-3.11Python 3.11.9, blosc2 4.11.0.dev0, numpy 2.4.6 — module
macos-latest-3.14Python 3.14.6, blosc2 4.11.0.dev0, numpy 2.5.2 — module
ubuntu-latest-3.11Python 3.11.15, blosc2 4.11.0.dev0, numpy 2.4.6 — module
ubuntu-latest-3.14Python 3.14.6, blosc2 4.11.0.dev0, numpy 2.5.2 — module
windows-latest-3.11Python 3.11.9, blosc2 4.11.0.dev0, numpy 2.4.6 — module
windows-latest-3.14Python 3.14.6, blosc2 4.11.0.dev0, numpy 2.5.2 — module
Worst non-noise ratio: 1.287x (where(a>0.5,a,b) (4M f64) on macos-latest-3.14) 1 benchmark(s) past threshold:
|
At three rounds one cell (macos-latest / 3.14) flagged where() at 1.287x along with the other large-array compute benchmarks, while the call-overhead-bound benchmarks on that same cell -- compress2 of 8 KB buffers at 0.986x, SChunk attribute access at 1.040x -- were clean. That is backwards: abi3 taxes the Python/C boundary, so a cost has to appear on the call-heavy benchmarks before it appears on 4M-element vectorized compute. The same where() benchmark came in at 0.987x, 1.009x and 0.971x on the other three cells, and 0.969x locally. So this is a noisy shared runner, not an ABI effect. Five rounds gives the per-benchmark minimum a larger sample to draw an uncontaminated observation from, which is the point of using min as the estimator. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
abi3 vs. version-specific buildMinimum of interleaved rounds. Regression threshold 1.25x (benchmarks under 5 ms are reported but never fail the job -- CI runners cannot resolve them). macos-latest-3.11Python 3.11.9, blosc2 4.11.0.dev0, numpy 2.4.6 — module
macos-latest-3.14Python 3.14.6, blosc2 4.11.0.dev0, numpy 2.5.2 — module
ubuntu-latest-3.11Python 3.11.15, blosc2 4.11.0.dev0, numpy 2.4.6 — module
ubuntu-latest-3.14Python 3.14.6, blosc2 4.11.0.dev0, numpy 2.5.2 — module
windows-latest-3.11Python 3.11.9, blosc2 4.11.0.dev0, numpy 2.4.6 — module
windows-latest-3.14Python 3.14.6, blosc2 4.11.0.dev0, numpy 2.5.2 — module
Worst non-noise ratio: 1.225x (CTable utf8 ingest 200k rows on macos-latest-3.11) No regression past threshold on any platform. ✅ |
It has done its job. Across six platform/version cells the abi3 build showed no cost: worst ratio 1.075x on Linux and 1.054x on Windows over every benchmark. Both specific worries came back negative -- Windows, where an abi3 extension calls through the python3.dll forwarder, was the cleanest platform in the matrix, and 3.11 running a binary compiled to the 3.11 Limited API showed nothing. The macOS runners turned out to resolve no better than about +/-25% (the same benchmark swung 1.287x on one run and 1.022x on the next, and another posted a 0.722x "speedup"), so the Linux and Windows cells carry the evidence. An outlier that relocates between runs is noise; a real ABI cost would land on the call-heavy benchmarks first and stay put. Not kept as a permanent job: it doubles every wheel build to produce a comparison, and the platform where it would matter -- manylinux aarch64 -- is the one it never covered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
This seems goo to go. The worse slowdown is At any rate, reducing the number of wheels seems a good thing, so merging. |
No description provided.