Skip to content

Commit 959dfdd

Browse files
committed
Added benchmarks to measure how the new adapters play out
* fixes #137 * fixed extraneous allocations inside the adapter's Registry Signed-off-by: Frédéric BIDON <[email protected]>
1 parent c3abe4a commit 959dfdd

29 files changed

+1910
-77
lines changed

.codecov.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
ignore:
22
- jsonutils/fixtures_test
33
- jsonutils/adapters/ifaces/mocks
4+
- jsonutils/adapters/testintegration/benchmarks

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@ dependencies outside of the standard library.
6565
**New with this release**:
6666

6767
* requires `go1.24`, as iterators are being introduced
68-
* removes the dependency to `mailru/easyjson` by default
68+
* removes the dependency to `mailru/easyjson` by default (#68)
6969
* functionality remains the same, but performance may somewhat degrade for applications
7070
that relied on `easyjson`
7171
* users of the JSON or YAML utilities who want to use `easyjson` as their prefered JSON seriliazer library
7272
will be able to do so by registering this the corresponding JSON adapter at runtime. See below.
7373
* ordered keys in JSON and YAML objects: this feature used to rely solely on `easyjson`.
7474
With this release, an implementation relying on the standard `encoding/json` is provided.
75+
* improved float is integer check (`conv.IsFloat64AJSONInteger`) (#59)
7576

7677
**What coming next?**
7778

go.work

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
go 1.24.0
2-
31
use (
42
.
53
./cmdutils
@@ -9,6 +7,7 @@ use (
97
./jsonutils
108
./jsonutils/adapters/easyjson
119
./jsonutils/adapters/testintegration
10+
./jsonutils/adapters/testintegration/benchmarks
1211
./jsonutils/fixtures_test
1312
./loading
1413
./mangling
@@ -18,19 +17,4 @@ use (
1817
./yamlutils
1918
)
2019

21-
// replace (
22-
// github.com/go-openapi/swag/cmdutils => ./cmdutils
23-
// github.com/go-openapi/swag/conv => ./conv
24-
// github.com/go-openapi/swag/fileutils => ./fileutils
25-
// github.com/go-openapi/swag/jsonname => ./jsonname
26-
// github.com/go-openapi/swag/jsonutils => ./jsonutils
27-
// github.com/go-openapi/swag/jsonutils/adapters/easyjson => ./jsonutils/adapters/easyjson
28-
// github.com/go-openapi/swag/jsonutils/adapters/testintegration => ./jsonutils/adapters/testintegration
29-
// github.com/go-openapi/swag/jsonutils/fixtures_test => ./jsonutils/fixtures_test
30-
// github.com/go-openapi/swag/loading => ./loading
31-
// github.com/go-openapi/swag/mangling => ./mangling
32-
// github.com/go-openapi/swag/netutils => ./netutils
33-
// github.com/go-openapi/swag/stringutils => ./stringutils
34-
// github.com/go-openapi/swag/typeutils => ./typeutils
35-
// github.com/go-openapi/swag/yamlutils => ./yamlutils
36-
// )
20+
go 1.24.0

jsonutils/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,5 @@ For example, to enable `easyjson` to be used in `ReadJSON` and `WriteJSON`, you
104104

105105
You may register several adapters. In this case, capability matching is evaluated from the last registered
106106
adapters (LIFO).
107+
108+
## [Benchmarks](./adapters/testintegration/benchmarks/README.md)

jsonutils/adapters/easyjson/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ require (
55
github.com/go-openapi/swag/jsonutils v0.0.0-00010101000000-000000000000
66
github.com/go-openapi/swag/jsonutils/fixtures_test v0.0.0-00010101000000-000000000000
77
github.com/go-openapi/swag/typeutils v0.0.0-00010101000000-000000000000
8-
github.com/mailru/easyjson v0.9.0
8+
github.com/mailru/easyjson v0.9.1
99
github.com/stretchr/testify v1.11.1
1010
)
1111

jsonutils/adapters/easyjson/go.sum

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
66
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
77
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
88
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
9-
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
10-
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
9+
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
1110
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1211
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1312
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=

jsonutils/adapters/easyjson/json/adapter.go

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

147+
func (a *Adapter) redeem() {
148+
RedeemAdapter(a)
149+
}
150+
147151
func newJWriter() *jwriter.Writer {
148152
return &jwriter.Writer{
149153
Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty,

jsonutils/adapters/easyjson/json/register.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,11 @@ func Register(dispatcher ifaces.Registrar, opts ...Option) {
3939
ifaces.RegistryEntry{
4040
Who: fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()),
4141
What: ifaces.AllCapabilities,
42-
Constructor: func() ifaces.Adapter {
42+
Constructor: func() (ifaces.Adapter, func()) {
4343
a := BorrowAdapter()
4444
a.options = o
4545

46-
return a
47-
},
48-
Redeemer: func(a ifaces.Adapter) {
49-
adapter := a.(*Adapter)
50-
RedeemAdapter(adapter)
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.
5147
},
5248
Support: support,
5349
})

jsonutils/adapters/ifaces/registry_iface.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,7 @@ const (
7878
type RegistryEntry struct {
7979
Who string
8080
What Capabilities
81-
Constructor func() Adapter
82-
Redeemer func(Adapter)
81+
Constructor func() (Adapter, func())
8382
Support func(what Capability, value any) bool
8483
}
8584

jsonutils/adapters/registry.go

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func (e registryError) Error() string {
4242
// ErrRegistry indicates an error returned by the [Registrar].
4343
var ErrRegistry registryError = "JSON adapters registry error"
4444

45-
type registry []ifaces.RegistryEntry
45+
type registry []*ifaces.RegistryEntry
4646

4747
// Registrar holds registered [ifaces.Adapters] for different serialization capabilities.
4848
//
@@ -57,54 +57,79 @@ type Registrar struct {
5757
gmx sync.RWMutex
5858

5959
// cache indexed by value type, so we don't have to lookup
60-
marshalerCache map[reflect.Type]ifaces.RegistryEntry
61-
unmarshalerCache map[reflect.Type]ifaces.RegistryEntry
62-
orderedMarshalerCache map[reflect.Type]ifaces.RegistryEntry
63-
orderedUnmarshalerCache map[reflect.Type]ifaces.RegistryEntry
64-
orderedMapCache map[reflect.Type]ifaces.RegistryEntry
60+
marshalerCache map[reflect.Type]*ifaces.RegistryEntry
61+
unmarshalerCache map[reflect.Type]*ifaces.RegistryEntry
62+
orderedMarshalerCache map[reflect.Type]*ifaces.RegistryEntry
63+
orderedUnmarshalerCache map[reflect.Type]*ifaces.RegistryEntry
64+
orderedMapCache map[reflect.Type]*ifaces.RegistryEntry
6565
}
6666

6767
func NewRegistrar() *Registrar {
6868
r := &Registrar{}
6969

70-
r.marshalerCache = make(map[reflect.Type]ifaces.RegistryEntry)
71-
r.unmarshalerCache = make(map[reflect.Type]ifaces.RegistryEntry)
72-
r.orderedMarshalerCache = make(map[reflect.Type]ifaces.RegistryEntry)
73-
r.orderedUnmarshalerCache = make(map[reflect.Type]ifaces.RegistryEntry)
74-
r.orderedMapCache = make(map[reflect.Type]ifaces.RegistryEntry)
70+
r.marshalerRegistry = make(registry, 0, 1)
71+
r.unmarshalerRegistry = make(registry, 0, 1)
72+
r.orderedMarshalerRegistry = make(registry, 0, 1)
73+
r.orderedUnmarshalerRegistry = make(registry, 0, 1)
74+
r.orderedMapRegistry = make(registry, 0, 1)
75+
76+
r.marshalerCache = make(map[reflect.Type]*ifaces.RegistryEntry)
77+
r.unmarshalerCache = make(map[reflect.Type]*ifaces.RegistryEntry)
78+
r.orderedMarshalerCache = make(map[reflect.Type]*ifaces.RegistryEntry)
79+
r.orderedUnmarshalerCache = make(map[reflect.Type]*ifaces.RegistryEntry)
80+
r.orderedMapCache = make(map[reflect.Type]*ifaces.RegistryEntry)
7581

7682
defaultRegistered(r)
7783

7884
return r
7985
}
8086

87+
// ClearCache resets the internal type cache.
88+
func (r *Registrar) ClearCache() {
89+
r.gmx.Lock()
90+
r.clearCache()
91+
r.gmx.Unlock()
92+
}
93+
94+
// Reset the [Registrar] to its defaults.
95+
func (r *Registrar) Reset() {
96+
r.gmx.Lock()
97+
r.clearCache()
98+
r.marshalerRegistry = r.marshalerRegistry[:0]
99+
r.unmarshalerRegistry = r.unmarshalerRegistry[:0]
100+
r.orderedMarshalerRegistry = r.orderedMarshalerRegistry[:0]
101+
r.orderedUnmarshalerRegistry = r.orderedUnmarshalerRegistry[:0]
102+
r.orderedMapRegistry = r.orderedMapRegistry[:0]
103+
r.gmx.Unlock()
104+
}
105+
81106
// RegisterFor registers an adapter for some JSON capabilities.
82107
func (r *Registrar) RegisterFor(entry ifaces.RegistryEntry) {
83108
r.gmx.Lock()
84109
if entry.What.Has(ifaces.CapabilityMarshalJSON) {
85110
e := entry
86111
e.What &= ifaces.Capabilities(ifaces.CapabilityMarshalJSON)
87-
r.marshalerRegistry = slices.Insert(r.marshalerRegistry, 0, e)
112+
r.marshalerRegistry = slices.Insert(r.marshalerRegistry, 0, &e)
88113
}
89114
if entry.What.Has(ifaces.CapabilityUnmarshalJSON) {
90115
e := entry
91116
e.What &= ifaces.Capabilities(ifaces.CapabilityUnmarshalJSON)
92-
r.unmarshalerRegistry = slices.Insert(r.unmarshalerRegistry, 0, e)
117+
r.unmarshalerRegistry = slices.Insert(r.unmarshalerRegistry, 0, &e)
93118
}
94119
if entry.What.Has(ifaces.CapabilityOrderedMarshalJSON) {
95120
e := entry
96121
e.What &= ifaces.Capabilities(ifaces.CapabilityOrderedMarshalJSON)
97-
r.orderedMarshalerRegistry = slices.Insert(r.orderedMarshalerRegistry, 0, e)
122+
r.orderedMarshalerRegistry = slices.Insert(r.orderedMarshalerRegistry, 0, &e)
98123
}
99124
if entry.What.Has(ifaces.CapabilityOrderedUnmarshalJSON) {
100125
e := entry
101126
e.What &= ifaces.Capabilities(ifaces.CapabilityOrderedUnmarshalJSON)
102-
r.orderedUnmarshalerRegistry = slices.Insert(r.orderedUnmarshalerRegistry, 0, e)
127+
r.orderedUnmarshalerRegistry = slices.Insert(r.orderedUnmarshalerRegistry, 0, &e)
103128
}
104129
if entry.What.Has(ifaces.CapabilityOrderedMap) {
105130
e := entry
106131
e.What &= ifaces.Capabilities(ifaces.CapabilityOrderedMap)
107-
r.orderedMapRegistry = slices.Insert(r.orderedMapRegistry, 0, e)
132+
r.orderedMapRegistry = slices.Insert(r.orderedMapRegistry, 0, &e)
108133
}
109134
r.gmx.Unlock()
110135
}
@@ -119,16 +144,22 @@ func (r *Registrar) AdapterFor(capability ifaces.Capability, value any) (ifaces.
119144
return nil, noopRedeemer
120145
}
121146

122-
adapter := entry.Constructor()
123-
if entry.Redeemer != nil {
124-
return adapter, func() {
125-
entry.Redeemer(adapter)
126-
}
147+
adapter, redeem := entry.Constructor()
148+
if redeem != nil {
149+
return adapter, redeem
127150
}
128151

129152
return adapter, noopRedeemer
130153
}
131154

155+
func (r *Registrar) clearCache() {
156+
clear(r.marshalerCache)
157+
clear(r.unmarshalerCache)
158+
clear(r.orderedMarshalerCache)
159+
clear(r.orderedUnmarshalerCache)
160+
clear(r.orderedMapCache)
161+
}
162+
132163
func (r *Registrar) findFirstFor(capability ifaces.Capability, value any) *ifaces.RegistryEntry {
133164
switch capability {
134165
case ifaces.CapabilityMarshalJSON:
@@ -146,13 +177,13 @@ func (r *Registrar) findFirstFor(capability ifaces.Capability, value any) *iface
146177
}
147178
}
148179

149-
func (r *Registrar) findFirstInRegistryFor(reg registry, cache map[reflect.Type]ifaces.RegistryEntry, capability ifaces.Capability, value any) *ifaces.RegistryEntry {
180+
func (r *Registrar) findFirstInRegistryFor(reg registry, cache map[reflect.Type]*ifaces.RegistryEntry, capability ifaces.Capability, value any) *ifaces.RegistryEntry {
150181
r.gmx.RLock()
151182
if len(reg) > 1 {
152183
if entry, ok := cache[reflect.TypeOf(value)]; ok {
153184
// cache hit
154185
r.gmx.RUnlock()
155-
return &entry
186+
return entry
156187
}
157188
}
158189

@@ -168,7 +199,7 @@ func (r *Registrar) findFirstInRegistryFor(reg registry, cache map[reflect.Type]
168199
cache[reflect.TypeOf(value)] = entry
169200
r.gmx.Unlock()
170201

171-
return &entry
202+
return entry
172203
}
173204

174205
// no adapter found

0 commit comments

Comments
 (0)