Skip to content

perf: parse fractional byte sizes exactly#20

Open
tisonkun wants to merge 3 commits into
mainfrom
codex/issue-2-exact-parser
Open

perf: parse fractional byte sizes exactly#20
tisonkun wants to merge 3 commits into
mainfrom
codex/issue-2-exact-parser

Conversation

@tisonkun

@tisonkun tisonkun commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace the bounded mantissa/exponent parser with an exact, allocation-free decimal conversion.
  • Accept arbitrarily precise fractional inputs while preserving the existing suffix grammar and round-half-up behavior.
  • Cover false overflow, double rounding, trailing-zero invariance, and binary rounding boundaries with regression and QuickCheck tests.
  • Add Divan parsing benchmarks across all workspace benchmark targets through cargo x bench, preserving the Rust 1.85 MSRV.
  • Verify cargo x test, cargo x lint, the Rust 1.85 test and benchmark paths, and stable/nightly no_std cross-target builds.

Closes #2.

Design Notes

The fractional conversion uses right-to-left base-10 scalar multiplication by the unit multiplier. After all fractional digits are consumed, the carry is the integral byte contribution and the final remainder digit determines the single round-half-up step. The largest supported multiplier is 2^60, so every intermediate remains within u64 and the implementation stays no_std and allocation-free.

On an Apple M4 Max, same-host Criterion comparisons against main showed approximately 12% lower latency for plain integers, 25% for ordinary fractional units, and 21% for small fractional units. Grouped u64::MAX inputs remained effectively flat.

tisonkun added 2 commits July 26, 2026 07:29
Signed-off-by: tison <wander4096@gmail.com>
Signed-off-by: tison <wander4096@gmail.com>
@tisonkun

Copy link
Copy Markdown
Contributor Author

Benchmark comparison: main vs this PR

I compared main@207df43 with this PR at 8cbf119 using the exact same Divan benchmark harness.

Environment and method:

  • Apple M4 Max (aarch64-apple-darwin)
  • Rust 1.85.0 and Divan 0.1.21
  • 4 warm-up pairs followed by 20 measured pairs
  • alternated main -> PR and PR -> main execution order to reduce thermal/order bias
  • each per-run number is Divan's median over 100 samples × 1,024 iterations
  • the table reports the median of those 20 per-run medians; change and IQR are calculated from paired runs

Lower is better. A negative change means the PR is faster.

Case main median PR median Paired change Paired change IQR
plain 4.584 ns 4.253 ns −9.7% −19.2% to −4.5%
decimal-unit 4.910 ns 4.623 ns −3.0% −15.2% to +3.2%
binary-unit 4.571 ns 4.464 ns −5.2% −6.6% to +4.3%
fraction 7.536 ns 6.029 ns −22.8% −24.4% to −17.3%
small-fraction 9.793 ns 7.856 ns −19.5% −25.1% to −13.0%
grouped 10.760 ns 10.380 ns −4.2% −9.2% to −0.6%
u64-max 18.330 ns 17.055 ns −8.7% −14.3% to −3.5%
high-precision-decimal 30.230 ns 32.635 ns +7.5% +4.9% to +9.7%
high-precision-binary 41.605 ns 122.000 ns +197.4% +187.7% to +221.7%
malformed 3.004 ns 2.752 ns −8.6% −10.8% to −5.1%

The result is favorable on the normal paths: ordinary fractional inputs improve by about 20–23%, while plain, grouped, maximum-integer, and malformed inputs improve by about 4–10%. The small changes for the two simple unit cases cross zero in the paired IQR, so I would treat them as noise rather than claim a win.

The two high-precision cases do more semantically correct work in the PR, so their timings are not like-for-like regressions:

  • For 1.84467440737095516145 EB, main double-rounds to 1844674407370955162; the PR returns the correct 1844674407370955161.
  • For the long ...3125 EiB half-byte boundary, main exits with Err(Malformed); the PR consumes the full fraction and correctly returns Ok(1). The roughly 3× cost is therefore the cost of accepting and evaluating an input that main rejects early.

CI reporting

The current CI workflow does not run benchmarks. For a lightweight first step, I would add a non-blocking benchmark job that compares base and head in the same runner, publishes the Markdown table through GITHUB_STEP_SUMMARY, and uploads the raw measurements as an artifact. I would not gate merges on wall-clock deltas from a shared GitHub-hosted runner for 3–120 ns operations.

For stable history, automatic base-branch comparison, and an updatable PR comment, CodSpeed's Divan integration is the cleaner option. A hand-written PR commenter needs pull-requests: write, while fork PR workflows normally receive a read-only token (GitHub documentation); a job summary avoids that permission/security complication.

Signed-off-by: tison <wander4096@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Faster and more accurate parsing

1 participant