Repository navigation
perf: speed up subnormal float parsing - #1237
arindamsikder wants to merge 2 commits into
Conversation
sobolevn
left a comment
There was a problem hiding this comment.
I am not a float expert, but I know one. I will ask this person to make a proper review.
| } | ||
| else { | ||
| if (MS_UNLIKELY(exponent > 288 || exponent < -307)) { | ||
| if (MS_UNLIKELY(exponent > 288 || exponent < -342)) { |
There was a problem hiding this comment.
please, document where these magic numbers come from.
| uint64_t x_hi = x.hi; | ||
| uint64_t x_lo = x.lo; | ||
|
|
||
| /* Round small values directly in units of 2**-1074, without first |
There was a problem hiding this comment.
Please, link this issue in the code.
There was a problem hiding this comment.
Also, this seems to be inspired by / lifted from the sources I suggested in #1232 (comment). We should document this. Both to credit the original authors / honour licenses, and also to know where the implementation comes from, in case something needs checking
There was a problem hiding this comment.
🔵 Needs a closer look
Low-level IEEE-754 rounding changes warrant maintainer review and hosted cross-platform CI validation.
0 open findings
What changed in this PR
Optimizes subnormal float parsing while preserving IEEE-754 rounding behavior.
Changes:
- Extends powers-of-ten support through
1e-342. - Adds direct subnormal rounding with exact-parser fallback.
- Adds correctness and performance coverage.
| File | Description |
|---|---|
src/msgspec/atof.h |
Implements subnormal fast-path rounding. |
src/msgspec/atof_consts.h |
Adds generated powers-of-ten constants. |
src/msgspec/_core.c |
Expands the fast-path exponent range. |
scripts/generate_atof_consts.py |
Generates the expanded table. |
tests/unit/test_json.py |
Tests rounding boundaries and decoding paths. |
tests/prof/perf/test_encode_decode.py |
Benchmarks small-float decoding. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Summary
Speed up JSON and lax string-to-float decoding of subnormal numbers without changing their IEEE-754 results.
Problem
#1232 reports extremely slow decoding of
[5e-324] * 65_536. Exponents below -307 currently go straight to the exact high-precision decimal parser. On this Linux x86-64 / CPython 3.11.15 / GCC 15 host, the reported array takes about 1.075 seconds on current main.Solution
Local medians from seven baseline/fixed rounds, reversing their order every round; each array has 65,536 values and outputs are checked bit-for-bit outside the timed region:
5e-3241e-3202.225073858507201e-3081e-3001.23456789123456.78901234567The ordinary controls changed by +0.35%, +0.14%, and -0.88% respectively in these medians. These are local measurements, not cross-platform guarantees. Ambiguous and very long decimal strings may still use HPD; this is not a general parser rewrite.
Testing
All builds/tests ran offline in a credential-free Bubblewrap namespace with environment, filesystem, network and source-import canaries. Equivalent underlying commands from the
justrecipes were invoked directly using the prepared dependency layer:python setup.py build_ext --inplace --force— passed; the same pre-existing_POSIX_C_SOURCEredefinition warning appears on main.python scripts/generate_atof_consts.py— generated the checked-in table.pytest -o testpaths=tests/unit -p no:cacheprovider --basetemp=/tmp/pytest -q -rs— 6,987 passed, 156 skipped. Pristine main with the same final dependency layer: 6,457 passed, 156 skipped. Skips are existing Python-version/optional-feature guards.pytest --doctest-modules --pyargs msgspec -p no:cacheprovider --basetemp=/tmp/pytest -q— 7 passed.ruff check,ruff format --check,python -m codespell_lib— passed (Ruff 0.16.9, Codespell 2.4.3). Unchanged main also passes these hooks.mypy tests/typing,pyright --pythonpath <resolved CPython executable> tests/typing,pyrefly check tests/typing,python scripts/stubtest.py— passed. Pyrefly emits the expected PYTHONPATH-environment notice; no type diagnostics. Explicit Pyright runtime selection avoids a sandbox interpreter-probe error.pytest tests/prof/perf/test_encode_decode.py -k 'decode_small_floats and amd' --codspeed --codspeed-mode walltime --codspeed-warmup-time 0.1 --codspeed-max-time 0.2 -p no:cacheprovider --basetemp=/tmp/pytest -q— 5 passed. This uses current pytest-codspeed 5.0.3 flags; the legacy--benchmark-name/--benchmark-disable-gcrecipe flags were not accepted by this installation.strtod, compiled with-fsanitize=undefined -fno-sanitize-recover=all; repeated with-U__SIZEOF_INT128__to exercise the portable multiplication path. Both passed: 6,312,610 accepted approximations, 4,962 exact-fallback requests, no mismatches or sanitizer findings.float(text).hex()through JSON and lax conversion — passed.Not run locally: non-Linux platforms, other Python versions, full Sphinx docs build (no docs changed), and hosted CI. Maintainer review and CI remain required; no merge is requested outside normal controls.
Related Issue
Fixes #1232.
Automation disclosure
Implemented and validated by Hermes DEV, an autonomous coding agent working for arindamsikder, with a separate AI reviewer. No human pre-review is claimed.