Repository navigation
Bulk step before json.dumps: no numpy in the float screen, blocked column screens, two-step subnormal search (closes #371) - #377
Merged
Conversation
…lumn screens, two-step subnormal search, no cycle check for scalar rows (refs #371) Main was slower than the 26.11.1 tag on plain scalar tables: without numpy a float column was judged by a function call per value (create_vertices 0.73x of the tag at 8 columns, 250,000 rows), and with numpy the five passes of the column screens over a table that does not fit the CPU cache cost more than the one pass of the old loop (insert_many 8 columns 0.90x). - The float column screen needs no numpy: its sum (NaN and the infinities make it non-finite; an overflow is a false alarm that the judgement by value then passes) and the smallest non-zero absolute value (a subnormal). One algorithm for both installs. - The column screens (insert_many's _rows_need_walk, create_vertices' screen_vertex_rows) run over blocks of about 16,384 values with the block as the outer loop, so the passes over a block (list, census, min and max, join, sum) hit the cache. - _dumps_checked looks for the subnormal exponent with the substring "e-3" first and runs the regular expression only when it is there (the expression cost about half of json.dumps on a text of digits, in the tag and in main). - Rows the census shows hold no list, tuple or dict are written with check_circular=False; rows the walk looked at keep it. The walk now runs before the text when the census asks for it, and after a refusal of json.dumps when it did not, so the first bad row in row order still gives the old error and message. Tests (pure Python, they run on both wheels): the float screen with and without numpy for every float value class, no per-value gate over a clean float column, the two-step search, a bad value of each class in every block (with a small block size) and in ragged, None and late-key rows, no cycle check for scalar rows and a cyclic row still raising what it raised, and a 12,000-row scalar batch stored through insert_many and create_vertices with its count and a content digest checked before and after a reopen (with and without a subnormal row). The new tests fail on main c90b3f9 (76 cases). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
tae898
force-pushed
the
perf/371-no-numpy-step
branch
from
November 12, 2026 01:53
1f6f39d to
47e8350
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
The step before
json.dumpsininsert_manyandGraphBatch.create_verticesis no longer slower than in the 26.11.1 tag, with numpy and without it. Main was slower on plain scalar tables: without numpy a float column was judged by a function call per value, and with numpy the five passes of the column screens over a table that does not fit the CPU cache cost more than the one pass of the old loop. One algorithm now, numpy is no longer used by the screens:sum(NaN and the infinities make it non-finite; an overflow is a false alarm that the judgement by value then passes) and the smallest non-zero absolute value (a subnormal)._dumps_checkedlooks for the subnormal exponent with the substringe-3first and runs the regular expression only when it is there. The expression cost about half ofjson.dumpson a text of digits, in the tag and in main.check_circular=False; rows the walk looked at keep it._check_rowsstays the exact reference. It runs before the text when the census asks for it and after a refusal ofjson.dumpswhen it did not, so the first bad row in row order still gives the old exception type and message.I did not keep the old per-row loop as a no-numpy branch: the blocked column screens are cheaper than the loop with and without numpy (table below), so two paths would only add code.
Related issues
Closes #371
Performance changes
Only the Python-side step is timed (no engine, no JVM, one core), not end-to-end ingest: median CPU microseconds per row, the tag 26.11.1 (
2f28a328be), main (c90b3f9), this branch (type_conversion.pysha2566715a4e09b54b90c), and the plainjson.dumpsof the same rows. Interleaved, warmed, 21, 7 or 5 repeats, the order alternating, with a control of main against itself in the same run. The same nine cells as the #318 study, Intel Core Ultra X9 388H, cores 12,13 (LP-E), Python 3.12, numpy present and absent. A cell counts as faster or slower only when the ranges of the repeats are separated.numpy present (median CPU microseconds per row; ratio above 1 means cand is quicker)
numpy absent (median CPU microseconds per row; ratio above 1 means cand is quicker)
Acceptance, as written before the run:
The files are in the private bench notes (
repros/bind-371-fix-20261112):iter2run, with the fuzz of the new code against the tag and main (20,000 random row sets per arm, every value class, text or exception type and message equal, create_vertices verdicts equal to main's).Docs
None needs it: no page describes this internal step.
Checklist
c90b3f9in 76 cases)uvx pre-commit run --files <changed files>passes (black, isort, ruff, bandit clean)🤖 Generated with Claude Code