Skip to content

Commit 3a6100b

Browse files
committed
Improving routing performance and benchmark suite
Before this commit, all the node types were added to the same list of children nodes. Taking in consideration that only one Param and Any type of node could exist per node, two new node struct field were added to hold the references to those kind of nodes. This avoid the need to iterate through all the Static type nodes just to find one Param or Any type node. Those iterations could be performed multiple times in the same iteration of Router#Find. Removing the route comments of the Router benchmark tests. Updating the Router benchmarks tests to find the routes defined to each particular benchmark. Before, all the benchmarks tried to find only the GitHub API. Adding new router benchmarks to measure when the Router try to find routes that are not registered.
1 parent ad3be08 commit 3a6100b

File tree

2 files changed

+271
-87
lines changed

2 files changed

+271
-87
lines changed

router.go

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@ type (
1414
echo *Echo
1515
}
1616
node struct {
17-
kind kind
18-
label byte
19-
prefix string
20-
parent *node
21-
children children
22-
ppath string
23-
pnames []string
24-
methodHandler *methodHandler
17+
kind kind
18+
label byte
19+
prefix string
20+
parent *node
21+
staticChildrens children
22+
ppath string
23+
pnames []string
24+
methodHandler *methodHandler
25+
paramChildren *node
26+
anyChildren *node
2527
}
2628
kind uint8
2729
children []*node
@@ -44,6 +46,9 @@ const (
4446
skind kind = iota
4547
pkind
4648
akind
49+
50+
paramLabel = byte(':')
51+
anyLabel = byte('*')
4752
)
4853

4954
// NewRouter returns a new Router instance.
@@ -134,23 +139,32 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
134139
}
135140
} else if l < pl {
136141
// Split node
137-
n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames)
142+
n := newNode(cn.kind, cn.prefix[l:], cn, cn.staticChildrens, cn.methodHandler, cn.ppath, cn.pnames, cn.paramChildren, cn.anyChildren)
138143

139144
// Update parent path for all children to new node
140-
for _, child := range cn.children {
145+
for _, child := range cn.staticChildrens {
141146
child.parent = n
142147
}
148+
if cn.paramChildren != nil {
149+
cn.paramChildren.parent = n
150+
}
151+
if cn.anyChildren != nil {
152+
cn.anyChildren.parent = n
153+
}
143154

144155
// Reset parent node
145156
cn.kind = skind
146157
cn.label = cn.prefix[0]
147158
cn.prefix = cn.prefix[:l]
148-
cn.children = nil
159+
cn.staticChildrens = nil
149160
cn.methodHandler = new(methodHandler)
150161
cn.ppath = ""
151162
cn.pnames = nil
163+
cn.paramChildren = nil
164+
cn.anyChildren = nil
152165

153-
cn.addChild(n)
166+
// Only Static children could reach here
167+
cn.addStaticChild(n)
154168

155169
if l == sl {
156170
// At parent node
@@ -160,9 +174,10 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
160174
cn.pnames = pnames
161175
} else {
162176
// Create child node
163-
n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames)
177+
n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames, nil, nil)
164178
n.addHandler(method, h)
165-
cn.addChild(n)
179+
// Only Static children could reach here
180+
cn.addStaticChild(n)
166181
}
167182
} else if l < sl {
168183
search = search[l:]
@@ -173,9 +188,16 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
173188
continue
174189
}
175190
// Create child node
176-
n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames)
191+
n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames, nil, nil)
177192
n.addHandler(method, h)
178-
cn.addChild(n)
193+
switch t {
194+
case skind:
195+
cn.addStaticChild(n)
196+
case pkind:
197+
cn.paramChildren = n
198+
case akind:
199+
cn.anyChildren = n
200+
}
179201
} else {
180202
// Node already exists
181203
if h != nil {
@@ -190,46 +212,45 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
190212
}
191213
}
192214

193-
func newNode(t kind, pre string, p *node, c children, mh *methodHandler, ppath string, pnames []string) *node {
215+
func newNode(t kind, pre string, p *node, sc children, mh *methodHandler, ppath string, pnames []string, paramChildren, anyChildren *node) *node {
194216
return &node{
195-
kind: t,
196-
label: pre[0],
197-
prefix: pre,
198-
parent: p,
199-
children: c,
200-
ppath: ppath,
201-
pnames: pnames,
202-
methodHandler: mh,
217+
kind: t,
218+
label: pre[0],
219+
prefix: pre,
220+
parent: p,
221+
staticChildrens: sc,
222+
ppath: ppath,
223+
pnames: pnames,
224+
methodHandler: mh,
225+
paramChildren: paramChildren,
226+
anyChildren: anyChildren,
203227
}
204228
}
205229

206-
func (n *node) addChild(c *node) {
207-
n.children = append(n.children, c)
230+
func (n *node) addStaticChild(c *node) {
231+
n.staticChildrens = append(n.staticChildrens, c)
208232
}
209233

210-
func (n *node) findChild(l byte, t kind) *node {
211-
for _, c := range n.children {
212-
if c.label == l && c.kind == t {
234+
func (n *node) findStaticChild(l byte) *node {
235+
for _, c := range n.staticChildrens {
236+
if c.label == l {
213237
return c
214238
}
215239
}
216240
return nil
217241
}
218242

219243
func (n *node) findChildWithLabel(l byte) *node {
220-
for _, c := range n.children {
244+
for _, c := range n.staticChildrens {
221245
if c.label == l {
222246
return c
223247
}
224248
}
225-
return nil
226-
}
227-
228-
func (n *node) findChildByKind(t kind) *node {
229-
for _, c := range n.children {
230-
if c.kind == t {
231-
return c
232-
}
249+
if l == paramLabel {
250+
return n.paramChildren
251+
}
252+
if l == anyLabel {
253+
return n.anyChildren
233254
}
234255
return nil
235256
}
@@ -356,7 +377,7 @@ func (r *Router) Find(method, path string, c Context) {
356377
// Attempt to go back up the tree on no matching prefix or no remaining search
357378
if l != pl || search == "" {
358379
// Handle special case of trailing slash route with existing any route (see #1526)
359-
if path[len(path)-1] == '/' && cn.findChildByKind(akind) != nil {
380+
if path[len(path)-1] == '/' && cn.anyChildren != nil {
360381
goto Any
361382
}
362383
if nn == nil { // Issue #1348
@@ -372,7 +393,7 @@ func (r *Router) Find(method, path string, c Context) {
372393
}
373394

374395
// Static node
375-
if child = cn.findChild(search[0], skind); child != nil {
396+
if child = cn.findStaticChild(search[0]); child != nil {
376397
// Save next
377398
if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
378399
nk = pkind
@@ -385,7 +406,7 @@ func (r *Router) Find(method, path string, c Context) {
385406

386407
Param:
387408
// Param node
388-
if child = cn.findChildByKind(pkind); child != nil {
409+
if child = cn.paramChildren; child != nil {
389410
// Issue #378
390411
if len(pvalues) == n {
391412
continue
@@ -410,7 +431,7 @@ func (r *Router) Find(method, path string, c Context) {
410431

411432
Any:
412433
// Any node
413-
if cn = cn.findChildByKind(akind); cn != nil {
434+
if cn = cn.anyChildren; cn != nil {
414435
// If any node is found, use remaining path for pvalues
415436
pvalues[len(cn.pnames)-1] = search
416437
break
@@ -424,7 +445,7 @@ func (r *Router) Find(method, path string, c Context) {
424445
search = ns
425446
np := nn.parent
426447
// Consider param route one level up only
427-
if cn = nn.findChildByKind(pkind); cn != nil {
448+
if cn = nn.paramChildren; cn != nil {
428449
pos := strings.IndexByte(ns, '/')
429450
if pos == -1 {
430451
// If no slash is remaining in search string set param value
@@ -441,7 +462,7 @@ func (r *Router) Find(method, path string, c Context) {
441462
// No param route found, try to resolve nearest any route
442463
for {
443464
np = nn.parent
444-
if cn = nn.findChildByKind(akind); cn != nil {
465+
if cn = nn.anyChildren; cn != nil {
445466
break
446467
}
447468
if np == nil {
@@ -472,7 +493,7 @@ func (r *Router) Find(method, path string, c Context) {
472493

473494
// Dig further for any, might have an empty value for *, e.g.
474495
// serving a directory. Issue #207.
475-
if cn = cn.findChildByKind(akind); cn == nil {
496+
if cn = cn.anyChildren; cn == nil {
476497
return
477498
}
478499
if h := cn.findHandler(method); h != nil {

0 commit comments

Comments
 (0)