Skip to content

Commit 2e28a5a

Browse files
committed
perf(jsonutils): fixed Adapter's interface to reduce its overhead
* performance optimization (jsonutils): The Adapter structurally requires to call functions that return an interface. > It is difficult to get such functions to work without extra memory > allocations. > > By slightly modifying the interface, the Adapter now converts the > concrete types without any extra allocation. > > I've been able to verify by profiling a benchmark execution that > the overhead incurred by the adapter is very small compared > to the json Marshaling/Unmarshaling workload, even for small payloads. * benchmark results The benchmarks for jsonutils have been updated with a run on go1.25 (also compared with the new experimental greenteagc) and presented on a chart. Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
1 parent 959dfdd commit 2e28a5a

File tree

16 files changed

+785
-149
lines changed

16 files changed

+785
-149
lines changed

jsonutils/adapters/easyjson/json/adapter.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,17 @@ func (a *Adapter) NewOrderedMap(capacity int) ifaces.OrderedMap {
144144
return &m
145145
}
146146

147-
func (a *Adapter) redeem() {
147+
func (a *Adapter) Redeem() {
148+
if a == nil {
149+
return
150+
}
148151
RedeemAdapter(a)
149152
}
150153

154+
func (a *Adapter) Reset() {
155+
a.options = options{}
156+
}
157+
151158
func newJWriter() *jwriter.Writer {
152159
return &jwriter.Writer{
153160
Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty,

jsonutils/adapters/easyjson/json/pool.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package json
1717
import (
1818
"sync"
1919

20+
"github.com/go-openapi/swag/jsonutils/adapters/ifaces"
2021
"github.com/mailru/easyjson/buffer"
2122
"github.com/mailru/easyjson/jlexer"
2223
"github.com/mailru/easyjson/jwriter"
@@ -27,9 +28,11 @@ type adaptersPool struct {
2728
}
2829

2930
func (p *adaptersPool) Borrow() *Adapter {
30-
ptr := p.Get()
31+
return p.Get().(*Adapter)
32+
}
3133

32-
return ptr.(*Adapter)
34+
func (p *adaptersPool) BorrowIface() ifaces.Adapter {
35+
return p.Get().(*Adapter)
3336
}
3437

3538
func (p *adaptersPool) Redeem(a *Adapter) {
@@ -107,11 +110,22 @@ func BorrowAdapter() *Adapter {
107110
return poolOfAdapters.Borrow()
108111
}
109112

113+
func BorrowAdapterIface() ifaces.Adapter {
114+
return poolOfAdapters.BorrowIface()
115+
}
116+
110117
// RedeemAdapter redeems an [Adapter] to the pool, so it may be recycled.
111118
func RedeemAdapter(a *Adapter) {
112119
poolOfAdapters.Redeem(a)
113120
}
114121

122+
func RedeemAdapterIface(a ifaces.Adapter) {
123+
concrete, ok := a.(*Adapter)
124+
if ok {
125+
poolOfAdapters.Redeem(concrete)
126+
}
127+
}
128+
115129
// BorrowWriter borrows a [jwriter.Writer] from the pool, recycling already allocated instances.
116130
func BorrowWriter() *jwriter.Writer {
117131
return poolOfWriters.Borrow()

jsonutils/adapters/easyjson/json/register.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,10 @@ func Register(dispatcher ifaces.Registrar, opts ...Option) {
3737

3838
dispatcher.RegisterFor(
3939
ifaces.RegistryEntry{
40-
Who: fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()),
41-
What: ifaces.AllCapabilities,
42-
Constructor: func() (ifaces.Adapter, func()) {
43-
a := BorrowAdapter()
44-
a.options = o
45-
46-
return a, a.redeem // since we return an interface, we have an extra allocation here. There is not much we can do about it.
47-
},
48-
Support: support,
40+
Who: fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()),
41+
What: ifaces.AllCapabilities,
42+
Constructor: BorrowAdapterIface,
43+
Support: support,
4944
})
5045
}
5146

jsonutils/adapters/ifaces/ifaces.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,36 @@ type SetOrdered interface {
2323
type OrderedMap interface {
2424
Ordered
2525
SetOrdered
26+
2627
OrderedMarshalJSON() ([]byte, error)
2728
OrderedUnmarshalJSON([]byte) error
2829
}
2930

3031
// MarshalAdapter behaves likes the standard library [json.Marshal].
3132
type MarshalAdapter interface {
33+
Poolable
34+
3235
Marshal(any) ([]byte, error)
3336
}
3437

3538
// OrderedMarshalAdapter behaves likes the standard library [json.Marshal], preserving the order of keys in objects.
3639
type OrderedMarshalAdapter interface {
40+
Poolable
41+
3742
OrderedMarshal(Ordered) ([]byte, error)
3843
}
3944

4045
// UnmarshalAdapter behaves likes the standard library [json.Unmarshal].
4146
type UnmarshalAdapter interface {
47+
Poolable
48+
4249
Unmarshal([]byte, any) error
4350
}
4451

4552
// OrderedUnmarshalAdapter behaves likes the standard library [json.Unmarshal], preserving the order of keys in objects.
4653
type OrderedUnmarshalAdapter interface {
54+
Poolable
55+
4756
OrderedUnmarshal([]byte, SetOrdered) error
4857
}
4958

@@ -61,3 +70,12 @@ type OrderedAdapter interface {
6170
OrderedUnmarshalAdapter
6271
NewOrderedMap(capacity int) OrderedMap
6372
}
73+
74+
type Poolable interface {
75+
// Self-redeem: for [Adapter] s that are allocated from a pool.
76+
// The [Adapter] must not be used after calling [Redeem].
77+
Redeem()
78+
79+
// Reset the state of the [Adapter], if any.
80+
Reset()
81+
}

0 commit comments

Comments
 (0)