Skip to content

Commit 10828ca

Browse files
committed
working through model performance refactor
1 parent af72cd2 commit 10828ca

34 files changed

+1630
-247
lines changed

datamodel/low/base/example.go

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
1+
// Copyright 2022-2026 Princess B33f Heavy Industries / Dave Shanley
22
// SPDX-License-Identifier: MIT
33

44
package base
55

66
import (
77
"context"
88
"hash/maphash"
9+
"sync"
910

1011
"github.com/pb33f/libopenapi/datamodel/low"
1112
"github.com/pb33f/libopenapi/index"
@@ -29,6 +30,8 @@ type Example struct {
2930
RootNode *yaml.Node
3031
index *index.SpecIndex
3132
context context.Context
33+
nodeStore sync.Map
34+
reference low.Reference
3235
*low.Reference
3336
low.NodeMap
3437
}
@@ -86,14 +89,21 @@ func (ex *Example) Hash() uint64 {
8689
// Build extracts extensions and example value
8790
func (ex *Example) Build(ctx context.Context, keyNode, root *yaml.Node, idx *index.SpecIndex) error {
8891
ex.KeyNode = keyNode
89-
ex.Reference = new(low.Reference)
92+
ex.reference = low.Reference{}
93+
ex.Reference = &ex.reference
9094
if ok, _, ref := utils.IsNodeRefValue(root); ok {
9195
ex.SetReference(ref, root)
9296
}
9397
root = utils.NodeAlias(root)
9498
ex.RootNode = root
9599
utils.CheckForMergeNodes(root)
96-
ex.Nodes = low.ExtractNodes(ctx, root)
100+
ex.nodeStore = sync.Map{}
101+
ex.Nodes = &ex.nodeStore
102+
if len(root.Content) > 0 {
103+
ex.NodeMap.ExtractNodes(root, false)
104+
} else {
105+
ex.AddNode(root.Line, root)
106+
}
97107
ex.Extensions = low.ExtractExtensions(root)
98108
ex.context = ctx
99109
ex.index = idx
@@ -109,14 +119,7 @@ func (ex *Example) Build(ctx context.Context, keyNode, root *yaml.Node, idx *ind
109119
ValueNode: vn,
110120
}
111121

112-
// extract nodes for all value nodes down the tree.
113-
expChildNodes := low.ExtractNodesRecursive(ctx, vn)
114-
expChildNodes.Range(func(k, v interface{}) bool {
115-
if arr, ko := v.([]*yaml.Node); ko {
116-
ex.Nodes.Store(k, arr)
117-
}
118-
return true
119-
})
122+
low.MergeRecursiveNodesIfLineAbsent(ex.Nodes, vn)
120123
}
121124

122125
// OpenAPI 3.2+ dataValue field
@@ -127,14 +130,7 @@ func (ex *Example) Build(ctx context.Context, keyNode, root *yaml.Node, idx *ind
127130
ValueNode: dataVn,
128131
}
129132

130-
// extract nodes for all dataValue nodes down the tree.
131-
expChildNodes := low.ExtractNodesRecursive(ctx, dataVn)
132-
expChildNodes.Range(func(k, v interface{}) bool {
133-
if arr, ko := v.([]*yaml.Node); ko {
134-
ex.Nodes.Store(k, arr)
135-
}
136-
return true
137-
})
133+
low.MergeRecursiveNodesIfLineAbsent(ex.Nodes, dataVn)
138134
}
139135

140136
// OpenAPI 3.2+ serializedValue field
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2022-2026 Princess B33f Heavy Industries / Dave Shanley
2+
// SPDX-License-Identifier: MIT
3+
4+
package base
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/pb33f/libopenapi/datamodel/low"
11+
"github.com/pb33f/libopenapi/index"
12+
"go.yaml.in/yaml/v4"
13+
)
14+
15+
func benchmarkExampleRootNode(b *testing.B) *yaml.Node {
16+
b.Helper()
17+
18+
yml := `summary: hot
19+
description: cakes
20+
value:
21+
pizza:
22+
kind: oven
23+
toppings:
24+
- cheese
25+
- herbs
26+
yummy:
27+
yes: pizza
28+
dataValue:
29+
payload:
30+
nested:
31+
flag: true
32+
serializedValue: '{"pizza":true}'
33+
x-cake:
34+
sweet:
35+
maybe: yes`
36+
37+
var root yaml.Node
38+
if err := yaml.Unmarshal([]byte(yml), &root); err != nil {
39+
b.Fatalf("failed to unmarshal benchmark example: %v", err)
40+
}
41+
if len(root.Content) == 0 || root.Content[0] == nil {
42+
b.Fatal("failed to unmarshal benchmark example: empty root")
43+
}
44+
return root.Content[0]
45+
}
46+
47+
func BenchmarkExample_Build(b *testing.B) {
48+
rootNode := benchmarkExampleRootNode(b)
49+
idx := index.NewSpecIndex(rootNode)
50+
ctx := context.Background()
51+
52+
b.ReportAllocs()
53+
b.ResetTimer()
54+
55+
for i := 0; i < b.N; i++ {
56+
var ex Example
57+
if err := low.BuildModel(rootNode, &ex); err != nil {
58+
b.Fatalf("build model failed: %v", err)
59+
}
60+
if err := ex.Build(ctx, nil, rootNode, idx); err != nil {
61+
b.Fatalf("example build failed: %v", err)
62+
}
63+
}
64+
}

datamodel/low/base/example_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,17 @@ serializedValue: '{"name":"John Doe","age":30,"active":true}'`
280280
hash2 := n.Hash()
281281
assert.NotEqual(t, hash1, hash2)
282282
}
283+
284+
func TestExample_Build_ScalarRoot(t *testing.T) {
285+
var scalar yaml.Node
286+
require.NoError(t, yaml.Unmarshal([]byte("hello"), &scalar))
287+
288+
var ex Example
289+
require.NoError(t, low.BuildModel(scalar.Content[0], &ex))
290+
require.NoError(t, ex.Build(context.Background(), nil, scalar.Content[0], nil))
291+
292+
assert.NotNil(t, ex.Nodes)
293+
nodes := ex.GetNodes()
294+
assert.Len(t, nodes[scalar.Content[0].Line], 1)
295+
assert.Equal(t, "hello", nodes[scalar.Content[0].Line][0].Value)
296+
}

datamodel/low/base/schema_build.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
277277
_, expLabel, expNode := utils.FindKeyNodeFullTop(ExampleLabel, root.Content)
278278
if expNode != nil {
279279
s.Example = low.NodeReference[*yaml.Node]{Value: expNode, KeyNode: expLabel, ValueNode: expNode}
280-
mergeRecursiveNodesIfLineAbsent(s.Nodes, expNode)
280+
low.MergeRecursiveNodesIfLineAbsent(s.Nodes, expNode)
281281
}
282282

283283
_, expArrLabel, expArrNode := utils.FindKeyNodeFullTop(ExamplesLabel, root.Content)
@@ -292,7 +292,7 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
292292
ValueNode: expArrNode,
293293
KeyNode: expArrLabel,
294294
}
295-
mergeRecursiveNodesIfLineAbsent(s.Nodes, expArrNode)
295+
low.MergeRecursiveNodesIfLineAbsent(s.Nodes, expArrNode)
296296
}
297297
}
298298

@@ -324,7 +324,7 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
324324
discriminator.RootNode = discNode
325325
discriminator.Nodes = low.ExtractNodes(ctx, discNode)
326326
s.Discriminator = low.NodeReference[*Discriminator]{Value: &discriminator, KeyNode: discLabel, ValueNode: discNode}
327-
appendRecursiveNodes(&discriminator, discNode)
327+
low.AppendRecursiveNodes(&discriminator, discNode)
328328
}
329329

330330
_, extDocLabel, extDocNode := utils.FindKeyNodeFullTop(ExternalDocsLabel, root.Content)

datamodel/low/base/schema_build_coverage_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ func TestResolveSchemaBuildInput_NilAndRefFailures(t *testing.T) {
7878
}
7979

8080
func TestRecursiveSchemaNodeHelpers(t *testing.T) {
81-
mergeRecursiveNodesIfLineAbsent(nil, nil)
82-
appendRecursiveNodes(nil, nil)
81+
low.MergeRecursiveNodesIfLineAbsent(nil, nil)
82+
low.AppendRecursiveNodes(nil, nil)
8383

8484
var root yaml.Node
8585
require.NoError(t, yaml.Unmarshal([]byte("example:\n nested:\n value: ok\n"), &root))
@@ -89,7 +89,7 @@ func TestRecursiveSchemaNodeHelpers(t *testing.T) {
8989
blockedLine := node.Content[0].Line
9090
dst.Store(blockedLine, []*yaml.Node{{Value: "existing"}})
9191

92-
mergeRecursiveNodesIfLineAbsent(&dst, node)
92+
low.MergeRecursiveNodesIfLineAbsent(&dst, node)
9393

9494
_, blocked := dst.Load(blockedLine)
9595
assert.True(t, blocked)
@@ -105,6 +105,6 @@ func TestRecursiveSchemaNodeHelpers(t *testing.T) {
105105
assert.True(t, foundNested)
106106

107107
collector := &collectingAddNodes{}
108-
appendRecursiveNodes(collector, node)
108+
low.AppendRecursiveNodes(collector, node)
109109
assert.NotEmpty(t, collector.lines)
110110
}

datamodel/low/base/schema_build_helpers.go

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"context"
88
"errors"
99
"fmt"
10-
"sync"
1110

1211
"github.com/pb33f/libopenapi/datamodel/low"
1312
"github.com/pb33f/libopenapi/index"
@@ -186,52 +185,6 @@ func assignBuiltSchemaList(ctx context.Context, labelNode, valueNode *yaml.Node,
186185
return nil
187186
}
188187

189-
func mergeRecursiveNodesIfLineAbsent(dst *sync.Map, node *yaml.Node) {
190-
if dst == nil || node == nil {
191-
return
192-
}
193-
194-
blocked := make(map[int]bool)
195-
known := make(map[int]bool)
196-
nodeMap := &low.NodeMap{Nodes: dst}
197-
walkRecursiveNodes(node, func(current *yaml.Node) {
198-
line := current.Line
199-
if !known[line] {
200-
_, blocked[line] = dst.Load(line)
201-
known[line] = true
202-
}
203-
if !blocked[line] {
204-
nodeMap.AddNode(line, current)
205-
}
206-
})
207-
}
208-
209-
func appendRecursiveNodes(dst low.AddNodes, node *yaml.Node) {
210-
if dst == nil || node == nil {
211-
return
212-
}
213-
214-
walkRecursiveNodes(node, func(current *yaml.Node) {
215-
dst.AddNode(current.Line, current)
216-
})
217-
}
218-
219-
func walkRecursiveNodes(node *yaml.Node, visit func(*yaml.Node)) {
220-
if node == nil || visit == nil || node.Content == nil {
221-
return
222-
}
223-
224-
for i := 0; i < len(node.Content); i++ {
225-
current := node.Content[i]
226-
if current.Line != 0 {
227-
visit(current)
228-
}
229-
if len(current.Content) > 0 {
230-
walkRecursiveNodes(current, visit)
231-
}
232-
}
233-
}
234-
235188
func resolveSchemaBuildInput(ctx context.Context, valueNode *yaml.Node, idx *index.SpecIndex, errFormat string) (resolvedSchemaBuildInput, error) {
236189
resolved := resolvedSchemaBuildInput{
237190
ctx: ctx,

datamodel/low/node_map_merge.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2022-2026 Princess B33f Heavy Industries / Dave Shanley
2+
// SPDX-License-Identifier: MIT
3+
4+
package low
5+
6+
import (
7+
"sync"
8+
9+
"go.yaml.in/yaml/v4"
10+
)
11+
12+
// MergeRecursiveNodesIfLineAbsent walks a node tree and adds each discovered node to dst
13+
// unless that line already exists in the destination map.
14+
func MergeRecursiveNodesIfLineAbsent(dst *sync.Map, node *yaml.Node) {
15+
if dst == nil || node == nil {
16+
return
17+
}
18+
19+
blocked := make(map[int]bool)
20+
known := make(map[int]bool)
21+
nodeMap := &NodeMap{Nodes: dst}
22+
walkRecursiveNodes(node, func(current *yaml.Node) {
23+
line := current.Line
24+
if !known[line] {
25+
_, blocked[line] = dst.Load(line)
26+
known[line] = true
27+
}
28+
if !blocked[line] {
29+
nodeMap.AddNode(line, current)
30+
}
31+
})
32+
}
33+
34+
// AppendRecursiveNodes walks a node tree and appends each discovered node to dst.
35+
func AppendRecursiveNodes(dst AddNodes, node *yaml.Node) {
36+
if dst == nil || node == nil {
37+
return
38+
}
39+
40+
walkRecursiveNodes(node, func(current *yaml.Node) {
41+
dst.AddNode(current.Line, current)
42+
})
43+
}
44+
45+
func walkRecursiveNodes(node *yaml.Node, visit func(*yaml.Node)) {
46+
if node == nil || visit == nil || node.Content == nil {
47+
return
48+
}
49+
50+
for i := 0; i < len(node.Content); i++ {
51+
current := node.Content[i]
52+
if current.Line != 0 {
53+
visit(current)
54+
}
55+
if len(current.Content) > 0 {
56+
walkRecursiveNodes(current, visit)
57+
}
58+
}
59+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2022-2026 Princess B33f Heavy Industries / Dave Shanley
2+
// SPDX-License-Identifier: MIT
3+
4+
package low
5+
6+
import (
7+
"sync"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
"go.yaml.in/yaml/v4"
13+
)
14+
15+
type collectingAddNodes struct {
16+
lines []int
17+
}
18+
19+
func (c *collectingAddNodes) AddNode(key int, _ *yaml.Node) {
20+
c.lines = append(c.lines, key)
21+
}
22+
23+
func TestNodeMapMergeHelpers(t *testing.T) {
24+
MergeRecursiveNodesIfLineAbsent(nil, nil)
25+
AppendRecursiveNodes(nil, nil)
26+
walkRecursiveNodes(nil, nil)
27+
28+
var root yaml.Node
29+
require.NoError(t, yaml.Unmarshal([]byte("example:\n nested:\n value: ok\n"), &root))
30+
node := root.Content[0]
31+
32+
var dst sync.Map
33+
blockedLine := node.Content[0].Line
34+
dst.Store(blockedLine, []*yaml.Node{{Value: "existing"}})
35+
36+
MergeRecursiveNodesIfLineAbsent(&dst, node)
37+
38+
_, blocked := dst.Load(blockedLine)
39+
assert.True(t, blocked)
40+
41+
var foundNested bool
42+
dst.Range(func(key, value any) bool {
43+
if key.(int) == node.Content[1].Content[0].Line {
44+
foundNested = true
45+
}
46+
assert.NotNil(t, value)
47+
return true
48+
})
49+
assert.True(t, foundNested)
50+
51+
collector := &collectingAddNodes{}
52+
AppendRecursiveNodes(collector, node)
53+
assert.NotEmpty(t, collector.lines)
54+
55+
var walked []int
56+
walkRecursiveNodes(node, func(current *yaml.Node) {
57+
walked = append(walked, current.Line)
58+
})
59+
assert.NotEmpty(t, walked)
60+
}

0 commit comments

Comments
 (0)