Skip to content

perf: fast-path encoder type properties - #1105

Open
pelletier wants to merge 1 commit into
perf/encoder-classifyfrom
perf/encoder-type-props
Open

perf: fast-path encoder type properties#1105
pelletier wants to merge 1 commit into
perf/encoder-classifyfrom
perf/encoder-type-props

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 #1104.

What

Every encoded value consults its type's cached properties (TextMarshaler and unstable.Marshaler receivers, value-vs-table), paying a sync.Map lookup — an interface hash — per consultation; this was ~20% of struct marshaling. Three layers shortcut it:

  • builtinEncProps resolves the handful of builtin types generic documents are made of (string, bool, int, int64, float64, []interface{}, map[string]interface{}) with a kind dispatch and one pointer comparison — none of them can implement TextMarshaler.
  • A small MRU memo (16 entries) on the encoder state catches the few named types a document actually uses; the global sync.Map stays as the shared cold cache.
  • appendValue dispatches special types kind-first (they are all structs except json.Number), keeping common scalars away from four interface comparisons, and the new appendValueProps lets callers that already hold the properties skip the lookup entirely (used by the plan-facts PR next in the chain).

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: -2.58%
  • RealWorldViperWrite: -15.13%
  • Marshal/HugoFrontMatter: -13.55%
  • Marshal/ReferenceFile/map: -13.53%
  • Marshal/SimpleDocument/map: -7.10%
  • Marshal/ReferenceFile/struct: -4.25%
  • Marshal/SimpleDocument/struct: -3.87%
  • UnmarshalDataset/code: -2.64%
  • Unmarshal/SimpleDocument/struct: +2.55%
Full benchstat (sec/op, allocs/op)

sec/op

UnmarshalDataset/config  10.27m ± 1%  10.25m ± 2%  ~ (p=0.739 n=10)
UnmarshalDataset/canada  23.06m ± 1%  23.33m ± 3%  ~ (p=0.529 n=10)
UnmarshalDataset/citm_catalog  14.91m ± 1%  15.02m ± 1%  +0.76% (p=0.043 n=10)
UnmarshalDataset/twitter  3.569m ± 0%  3.571m ± 0%  ~ (p=0.436 n=10)
UnmarshalDataset/code  33.92m ± 3%  33.03m ± 2%  -2.64% (p=0.009 n=10)
UnmarshalDataset/example  77.08µ ± 2%  77.16µ ± 1%  ~ (p=0.684 n=10)
Unmarshal/SimpleDocument/struct  325.2n ± 4%  333.5n ± 1%  +2.55% (p=0.020 n=10)
Unmarshal/SimpleDocument/map  428.9n ± 1%  433.1n ± 1%  +0.96% (p=0.000 n=10)
Unmarshal/ReferenceFile/struct  39.20µ ± 4%  39.30µ ± 2%  ~ (p=0.739 n=10)
Unmarshal/ReferenceFile/map  31.24µ ± 1%  31.33µ ± 0%  ~ (p=0.118 n=10)
Unmarshal/HugoFrontMatter  5.617µ ± 1%  5.647µ ± 1%  ~ (p=0.165 n=10)
Marshal/SimpleDocument/struct  373.3n ± 1%  358.9n ± 1%  -3.87% (p=0.000 n=10)
Marshal/SimpleDocument/map  556.0n ± 3%  516.4n ± 1%  -7.10% (p=0.000 n=10)
Marshal/ReferenceFile/struct  18.55µ ± 3%  17.76µ ± 4%  -4.25% (p=0.004 n=10)
Marshal/ReferenceFile/map  36.39µ ± 2%  31.47µ ± 4%  -13.53% (p=0.000 n=10)
Marshal/HugoFrontMatter  7.268µ ± 2%  6.283µ ± 1%  -13.55% (p=0.000 n=10)
RealWorldContainerdConfig  39.78µ ± 1%  39.27µ ± 2%  -1.29% (p=0.001 n=10)
RealWorldViperRead  8.342µ ± 1%  8.314µ ± 1%  -0.34% (p=0.041 n=10)
RealWorldViperWrite  11.763µ ± 2%  9.984µ ± 2%  -15.13% (p=0.000 n=10)
RealWorldHugoFrontMatterBatch  94.68µ ± 1%  94.38µ ± 1%  ~ (p=0.393 n=10)
RealWorldGitleaksRules  64.77µ ± 1%  64.87µ ± 1%  ~ (p=0.853 n=10)
RealWorldPyproject  15.03µ ± 1%  14.93µ ± 1%  ~ (p=0.052 n=10)
RealWorldGolangciStrict  11.81µ ± 0%  11.77µ ± 0%  -0.30% (p=0.037 n=10)
geomean  45.51µ  44.33µ  -2.58%

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

Every encoded value consults its type's cached properties (TextMarshaler
and unstable.Marshaler receivers, value-vs-table), paying a sync.Map
lookup — an interface hash — per consultation; this was 20% of struct
marshaling. Three layers shortcut it:

- builtinEncProps resolves the handful of builtin types generic
  documents are made of with a kind dispatch and one pointer comparison.
- A small MRU memo on the encoder state catches the few named types a
  document actually uses.
- appendValue dispatches special types kind-first (they are all structs
  except json.Number), keeping common scalars away from the interface
  comparisons, and appendValueProps lets callers that already hold the
  properties skip the lookup entirely.

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