Skip to content

encoding/json: performance regression marshaling small values for goexperiment.jsonv2 #74617

@dsnet

Description

@dsnet

In order to fix bugs like #22967, #27722, or #33993, where the MarshalJSON method was not called on a pointer receiver, the v2 implementation consistently always ensures that a reflect.Value is addressable at the entry point (shallow copying a value on the heap if necessary). This makes the logic easier to reason about, since we can assume that a value is always addressable.

However, this leads to a performance regression relative to v1 when marshaling a non-pointer values, which will always allocate.

What did you do?

Consider this benchmark:

var src bool
b.ReportAllocs()
for range b.N {
	jsonv1.Marshal(src)
}

where src is a bool (rather than a *bool).

What did you see happen?

BenchmarkV1-32       	23085357	        52.64 ns/op	       8 B/op	       1 allocs/op
BenchmarkV1in2-32    	13171356	        81.62 ns/op	      16 B/op	       2 allocs/op

What did you expect to see?

BenchmarkV1-32       	23085357	        52.64 ns/op	       8 B/op	       1 allocs/op
BenchmarkV1in2-32    	23085357	        52.64 ns/op	       8 B/op	       1 allocs/op

I expect to see a single allocation for v1in2.

We could fix this by deferring the shallow copy until we actually need to call a method on a pointer receiver. This is functionally what v1 did, but the code was harder to reason about whether we will accidentally have a panic due to trying to call Addr on a non-addressable value.

\cc @neild @mvdan @johanbrandhorst

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugReportIssues describing a possible bug in the Go implementation.NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.Performance

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions