Skip to content

perf: specialize decimal digit scanning - #1097

Open
pelletier wants to merge 1 commit into
v2from
perf/decimal-digits
Open

perf: specialize decimal digit scanning#1097
pelletier wants to merge 1 commit into
v2from
perf/decimal-digits

Conversation

@pelletier

Copy link
Copy Markdown
Owner

Splitting #1088: this PR carries one optimization technique so its impact and review surface stay isolated.

What

scanDigitsWithUnderscores pays an indirect isInRange function call per byte. Decimal runs — every integer, float fraction, and exponent, the overwhelmingly common case — now scan through scanDecimalDigits, a specialized loop with the range test inlined and the same underscore validation (and error message). Hex, octal, and binary literals keep the generic scanner.

Impact

Benchmarked on a dedicated linux/amd64 spot VM (t2d), go1.26.4, interleaved A/B vs the base of this PR, benchstat over 10 samples per side:

  • sec/op geomean: -0.90%
  • UnmarshalDataset/canada: -13.09%
  • UnmarshalDataset/code: -6.49%
  • Unmarshal/SimpleDocument/map: -2.97%
  • RealWorldContainerdConfig: +1.59%
  • Marshal/HugoFrontMatter: +3.18%
Full benchstat (sec/op, allocs/op)

sec/op

UnmarshalDataset/config  10.43m ± 3%  10.28m ±  1%  -1.37% (p=0.009 n=10)
UnmarshalDataset/canada  23.59m ± 5%  20.50m ± 10%  -13.09% (p=0.000 n=10)
UnmarshalDataset/citm_catalog  15.12m ± 5%  15.12m ±  6%  ~ (p=0.853 n=10)
UnmarshalDataset/twitter  3.629m ± 4%  3.582m ±  5%  ~ (p=0.052 n=10)
UnmarshalDataset/code  34.00m ± 3%  31.80m ±  4%  -6.49% (p=0.000 n=10)
UnmarshalDataset/example  77.31µ ± 2%  77.09µ ±  3%  ~ (p=0.190 n=10)
Unmarshal/SimpleDocument/struct  335.8n ± 1%  333.4n ±  3%  ~ (p=0.239 n=10)
Unmarshal/SimpleDocument/map  425.4n ± 1%  412.7n ±  2%  -2.97% (p=0.001 n=10)
Unmarshal/ReferenceFile/struct  39.52µ ± 3%  39.60µ ±  1%  ~ (p=0.631 n=10)
Unmarshal/ReferenceFile/map  31.50µ ± 2%  31.75µ ±  2%  ~ (p=0.342 n=10)
Unmarshal/HugoFrontMatter  5.656µ ± 2%  5.718µ ±  3%  +1.11% (p=0.045 n=10)
Marshal/SimpleDocument/struct  398.1n ± 3%  392.2n ±  4%  ~ (p=0.118 n=10)
Marshal/SimpleDocument/map  596.4n ± 1%  602.4n ±  5%  +1.01% (p=0.027 n=10)
Marshal/ReferenceFile/struct  20.70µ ± 1%  20.29µ ±  3%  ~ (p=0.172 n=10)
Marshal/ReferenceFile/map  39.85µ ± 4%  40.29µ ±  4%  ~ (p=0.353 n=10)
Marshal/HugoFrontMatter  8.030µ ± 3%  8.286µ ±  6%  +3.18% (p=0.022 n=10)
RealWorldContainerdConfig  39.96µ ± 2%  40.60µ ±  2%  +1.59% (p=0.035 n=10)
RealWorldViperRead  8.342µ ± 1%  8.244µ ±  2%  ~ (p=0.190 n=10)
RealWorldViperWrite  12.37µ ± 5%  12.58µ ±  2%  ~ (p=0.684 n=10)
RealWorldHugoFrontMatterBatch  95.47µ ± 2%  95.55µ ±  2%  ~ (p=0.971 n=10)
RealWorldGitleaksRules  65.12µ ± 2%  65.23µ ±  1%  ~ (p=0.912 n=10)
RealWorldPyproject  15.20µ ± 2%  15.20µ ±  3%  ~ (p=1.000 n=10)
RealWorldGolangciStrict  11.83µ ± 3%  11.88µ ±  1%  ~ (p=0.469 n=10)
geomean  46.79µ  46.36µ  -0.90%

allocs/op

UnmarshalDataset/config  70.19k ± 0%  70.19k ± 0%  ~ (p=1.000 n=10) ¹
UnmarshalDataset/canada  223.2k ± 0%  223.2k ± 0%  ~ (p=1.000 n=10) ¹
UnmarshalDataset/citm_catalog  49.98k ± 0%  49.98k ± 0%  ~ (p=1.000 n=10) ¹
UnmarshalDataset/twitter  15.78k ± 0%  15.78k ± 0%  ~ (p=1.000 n=10) ¹
UnmarshalDataset/code  129.5k ± 0%  129.5k ± 0%  ~ (p=1.000 n=10) ¹
UnmarshalDataset/example  399.0 ± 0%  399.0 ± 0%  ~ (p=1.000 n=10) ¹
Unmarshal/SimpleDocument/struct  2.000 ± 0%  2.000 ± 0%  ~ (p=1.000 n=10) ¹
Unmarshal/SimpleDocument/map  5.000 ± 0%  5.000 ± 0%  ~ (p=1.000 n=10) ¹
Unmarshal/ReferenceFile/struct  83.00 ± 0%  83.00 ± 0%  ~ (p=1.000 n=10) ¹
Unmarshal/ReferenceFile/map  194.0 ± 0%  194.0 ± 0%  ~ (p=1.000 n=10) ¹
Unmarshal/HugoFrontMatter  54.00 ± 0%  54.00 ± 0%  ~ (p=1.000 n=10) ¹
Marshal/SimpleDocument/struct  4.000 ± 0%  4.000 ± 0%  ~ (p=1.000 n=10) ¹
Marshal/SimpleDocument/map  4.000 ± 0%  4.000 ± 0%  ~ (p=1.000 n=10) ¹
Marshal/ReferenceFile/struct  4.000 ± 0%  4.000 ± 0%  ~ (p=1.000 n=10) ¹
Marshal/ReferenceFile/map  91.00 ± 0%  91.00 ± 0%  ~ (p=1.000 n=10) ¹
Marshal/HugoFrontMatter  20.00 ± 0%  20.00 ± 0%  ~ (p=1.000 n=10) ¹
RealWorldContainerdConfig  144.0 ± 0%  144.0 ± 0%  ~ (p=1.000 n=10) ¹
RealWorldViperRead  65.00 ± 0%  65.00 ± 0%  ~ (p=1.000 n=10) ¹
RealWorldViperWrite  33.00 ± 0%  33.00 ± 0%  ~ (p=1.000 n=10) ¹
RealWorldHugoFrontMatterBatch  820.0 ± 0%  820.0 ± 0%  ~ (p=1.000 n=10) ¹
RealWorldGitleaksRules  259.0 ± 0%  259.0 ± 0%  ~ (p=1.000 n=10) ¹
RealWorldPyproject  142.0 ± 0%  142.0 ± 0%  ~ (p=1.000 n=10) ¹
RealWorldGolangciStrict  48.00 ± 0%  48.00 ± 0%  ~ (p=1.000 n=10) ¹
geomean  211.1  211.1  +0.00%
¹ all samples are equal

🤖 Generated with Claude Code

scanDigitsWithUnderscores pays an indirect isInRange call per byte.
Decimal runs — the overwhelmingly common case: every integer, float
fraction, and exponent — now scan through a specialized loop with the
range test inlined. Hex, octal, and binary literals keep the generic
scanner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pelletier pelletier added the performance Issue related to a performance problem or pull request improving performance. label Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Issue related to a performance problem or pull request improving performance.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant