Skip to content

perf: make strict-mode key tracking allocation-free on valid documents#1102

Open
pelletier wants to merge 1 commit into
perf/fused-containersfrom
perf/strict-lazy-keys
Open

perf: make strict-mode key tracking allocation-free on valid documents#1102
pelletier wants to merge 1 commit into
perf/fused-containersfrom
perf/strict-lazy-keys

Conversation

@pelletier

Copy link
Copy Markdown
Owner

Splitting #1088: this PR carries one optimization technique so its impact and review surface stay isolated. Stacked on #1101 (only to keep the diffs disjoint — the change itself is independent).

What

The strict-mode KeyTracker materialized a string for every key part pushed — one allocation per part per table header, on every document, even though the strings are only ever read when an unknown-key error is actually recorded. The tracker now keeps the raw part bytes (valid for the lifetime of the document being parsed) and materializes strings only when an error snapshot is taken (Key()); resetting also reuses the stack storage across pooled decodes instead of dropping it.

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.71%
  • Marshal/SimpleDocument/map: -3.80%
  • RealWorldGolangciStrict: -3.55%
  • Marshal/ReferenceFile/map: -2.41%
  • Unmarshal/SimpleDocument/map: -2.08%
  • UnmarshalDataset/config: -1.95%
  • RealWorldHugoFrontMatterBatch: -1.93%
  • RealWorldContainerdConfig: +2.07%
  • allocs/op geomean: -1.49%
  • allocs RealWorldGolangciStrict: -29.17%
Full benchstat (sec/op, allocs/op)

sec/op

UnmarshalDataset/config  10.032m ± 1%  9.836m ± 1%  -1.95% (p=0.001 n=10)
UnmarshalDataset/canada  23.54m ± 1%  23.66m ± 1%  ~ (p=0.089 n=10)
UnmarshalDataset/citm_catalog  8.695m ± 1%  8.570m ± 1%  -1.44% (p=0.000 n=10)
UnmarshalDataset/twitter  3.686m ± 1%  3.735m ± 1%  +1.33% (p=0.003 n=10)
UnmarshalDataset/code  32.01m ± 1%  31.92m ± 1%  ~ (p=0.529 n=10)
UnmarshalDataset/example  77.20µ ± 0%  77.79µ ± 4%  ~ (p=0.089 n=10)
Unmarshal/SimpleDocument/struct  345.6n ± 2%  347.6n ± 1%  ~ (p=0.148 n=10)
Unmarshal/SimpleDocument/map  447.1n ± 2%  437.8n ± 1%  -2.08% (p=0.000 n=10)
Unmarshal/ReferenceFile/struct  37.76µ ± 4%  38.06µ ± 3%  ~ (p=0.912 n=10)
Unmarshal/ReferenceFile/map  29.68µ ± 2%  29.80µ ± 1%  ~ (p=0.529 n=10)
Unmarshal/HugoFrontMatter  5.656µ ± 2%  5.653µ ± 3%  ~ (p=0.529 n=10)
Marshal/SimpleDocument/struct  395.0n ± 1%  398.9n ± 1%  +1.00% (p=0.003 n=10)
Marshal/SimpleDocument/map  628.5n ± 1%  604.5n ± 1%  -3.80% (p=0.000 n=10)
Marshal/ReferenceFile/struct  20.45µ ± 2%  20.34µ ± 1%  ~ (p=0.218 n=10)
Marshal/ReferenceFile/map  40.93µ ± 2%  39.95µ ± 2%  -2.41% (p=0.005 n=10)
Marshal/HugoFrontMatter  8.159µ ± 4%  7.919µ ± 4%  ~ (p=0.105 n=10)
RealWorldContainerdConfig  38.96µ ± 2%  39.76µ ± 2%  +2.07% (p=0.004 n=10)
RealWorldViperRead  8.417µ ± 2%  8.319µ ± 1%  ~ (p=0.089 n=10)
RealWorldViperWrite  12.56µ ± 4%  12.53µ ± 3%  ~ (p=0.912 n=10)
RealWorldHugoFrontMatterBatch  95.38µ ± 3%  93.54µ ± 1%  -1.93% (p=0.000 n=10)
RealWorldGitleaksRules  65.51µ ± 1%  65.30µ ± 1%  ~ (p=0.143 n=10)
RealWorldPyproject  14.33µ ± 3%  14.23µ ± 2%  ~ (p=0.105 n=10)
RealWorldGolangciStrict  12.14µ ± 2%  11.71µ ± 1%  -3.55% (p=0.000 n=10)
geomean  45.54µ  45.22µ  -0.71%

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%  34.00 ± 0%  -29.17% (p=0.000 n=10)
geomean  211.1  207.9  -1.49%
¹ all samples are equal

This is a decode-only change; the Marshal/* movements are code-alignment noise. The headline is the allocation column: −29% allocs/op on the strict-mode benchmark (RealWorldGolangciStrict).

🤖 Generated with Claude Code

The KeyTracker materialized a string for every key part pushed — one
allocation per part per table header, on every document, even though
the strings are only ever read when an unknown-key error is actually
recorded. The tracker now keeps the raw part bytes (valid for the
lifetime of the document being parsed) and materializes strings only
when an error snapshot is taken. Resetting the tracker also reuses its
stack storage across pooled decodes instead of dropping it.

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