From 33886c87586217ed97b9563df6d8f32f1e71078a Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Fri, 5 Jun 2015 15:08:32 -0700 Subject: [PATCH] Improved router performance Signed-off-by: Vishal Rana --- context.go | 4 +- examples/website/public/favicon.ico | Bin router.go | 89 ++++++++++++++++------------ router_test.go | 12 ++-- website/docs/guide.md | 2 +- 5 files changed, 59 insertions(+), 48 deletions(-) mode change 100755 => 100644 examples/website/public/favicon.ico diff --git a/context.go b/context.go index dfb4985a3..7c66de192 100644 --- a/context.go +++ b/context.go @@ -49,8 +49,8 @@ func (c *Context) Socket() *websocket.Conn { } // P returns path parameter by index. -func (c *Context) P(i uint8) (value string) { - l := uint8(len(c.pnames)) +func (c *Context) P(i int) (value string) { + l := len(c.pnames) if i <= l { value = c.pvalues[i] } diff --git a/examples/website/public/favicon.ico b/examples/website/public/favicon.ico old mode 100755 new mode 100644 diff --git a/router.go b/router.go index fcb21d378..f11d4bf13 100644 --- a/router.go +++ b/router.go @@ -4,9 +4,9 @@ import "net/http" type ( Router struct { - trees map[string]*node - routes []Route - echo *Echo + trees [21]*node + routes []Route + echo *Echo } node struct { typ ntype @@ -30,12 +30,12 @@ const ( func NewRouter(e *Echo) (r *Router) { r = &Router{ - trees: make(map[string]*node), + // trees: make(map[string]*node), routes: []Route{}, echo: e, } for _, m := range methods { - r.trees[m] = &node{ + r.trees[r.treeIndex(m)] = &node{ prefix: "", children: children{}, } @@ -81,13 +81,21 @@ func (r *Router) insert(method, path string, h HandlerFunc, t ntype, pnames []st *e.maxParam = l } - cn := r.trees[method] // Current node as root + cn := r.trees[r.treeIndex(method)] // Current node as root search := path for { sl := len(search) pl := len(cn.prefix) - l := lcp(search, cn.prefix) + l := 0 + + // LCP + max := pl + if sl < max { + max = sl + } + for ; l < max && search[l] == cn.prefix[l]; l++ { + } if l == 0 { // At root node @@ -102,16 +110,18 @@ func (r *Router) insert(method, path string, h HandlerFunc, t ntype, pnames []st } else if l < pl { // Split node n := newNode(cn.typ, cn.prefix[l:], cn, cn.children, cn.handler, cn.pnames, cn.echo) - cn.children = children{n} // Add to parent // Reset parent node cn.typ = stype cn.label = cn.prefix[0] cn.prefix = cn.prefix[:l] + cn.children = nil cn.handler = nil cn.pnames = nil cn.echo = nil + cn.addChild(n) + if l == sl { // At parent node cn.typ = t @@ -121,11 +131,11 @@ func (r *Router) insert(method, path string, h HandlerFunc, t ntype, pnames []st } else { // Create child node n = newNode(t, search[l:], cn, nil, h, pnames, e) - cn.children = append(cn.children, n) + cn.addChild(n) } } else if l < sl { search = search[l:] - c := cn.findChild(search[0]) + c := cn.findChildWithLabel(search[0]) if c != nil { // Go deeper cn = c @@ -133,7 +143,7 @@ func (r *Router) insert(method, path string, h HandlerFunc, t ntype, pnames []st } // Create child node n := newNode(t, search, cn, nil, h, pnames, e) - cn.children = append(cn.children, n) + cn.addChild(n) } else { // Node already exists if h != nil { @@ -159,56 +169,47 @@ func newNode(t ntype, pre string, p *node, c children, h HandlerFunc, pnames []s } } -func (n *node) findChild(l byte) *node { - for _, c := range n.children { - if c.label == l { - return c - } - } - return nil +func (n *node) addChild(c *node) { + n.children = append(n.children, c) } -func (n *node) findSchild(l byte) *node { +func (n *node) findChild(l byte, t ntype) *node { for _, c := range n.children { - if c.label == l && c.typ == stype { + if c.label == l && c.typ == t { return c } } return nil } -func (n *node) findPchild() *node { +func (n *node) findChildWithLabel(l byte) *node { for _, c := range n.children { - if c.typ == ptype { + if c.label == l { return c } } return nil } -func (n *node) findMchild() *node { +func (n *node) findChildWithType(t ntype) *node { for _, c := range n.children { - if c.typ == mtype { + if c.typ == t { return c } } return nil } -// Length of longest common prefix -func lcp(a, b string) (i int) { - max := len(a) - l := len(b) - if l < max { - max = l - } - for ; i < max && a[i] == b[i]; i++ { +func (r *Router) treeIndex(method string) uint8 { + if method[0] == 'P' { + return method[0]%10 + method[1] - 65 + } else { + return method[0] % 10 } - return } func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo) { - cn := r.trees[method] // Current node as root + cn := r.trees[r.treeIndex(method)] // Current node as root search := path var ( @@ -235,8 +236,16 @@ func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo l := 0 // LCP length if cn.label != ':' { + sl := len(search) pl = len(cn.prefix) - l = lcp(search, cn.prefix) + + // LCP + max := pl + if sl < max { + max = sl + } + for ; l < max && search[l] == cn.prefix[l]; l++ { + } } if l == pl { @@ -257,7 +266,7 @@ func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo if search == "" { // TODO: Needs improvement - if cn.findMchild() == nil { + if cn.findChildWithType(mtype) == nil { continue } // Empty value @@ -265,7 +274,7 @@ func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo } // Static node - c = cn.findSchild(search[0]) + c = cn.findChild(search[0], stype) if c != nil { // Save next if cn.label == '/' { @@ -279,7 +288,7 @@ func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo // Param node Param: - c = cn.findPchild() + c = cn.findChildWithType(ptype) if c != nil { // Save next if cn.label == '/' { @@ -299,13 +308,15 @@ func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo // Match-any node MatchAny: - c = cn.findMchild() + // c = cn.getChild() + c = cn.findChildWithType(mtype) if c != nil { cn = c ctx.pvalues[0] = search search = "" // End search continue } + // Not found return } diff --git a/router_test.go b/router_test.go index 4370163dc..caaadd740 100644 --- a/router_test.go +++ b/router_test.go @@ -10,7 +10,7 @@ import ( ) var ( - api = []Route{ + api = []Route{ // OAuth Authorizations {"GET", "/authorizations", nil}, {"GET", "/authorizations/:id", nil}, @@ -520,11 +520,6 @@ func TestRouterAPI(t *testing.T) { for _, route := range api { r.Add(route.Method, route.Path, func(c *Context) error { - for i, n := range c.pnames { - if assert.NotEmpty(t, n) { - assert.Equal(t, ":"+n, c.P(uint8(i))) - } - } return nil }, e) } @@ -532,6 +527,11 @@ func TestRouterAPI(t *testing.T) { for _, route := range api { h, _ := r.Find(route.Method, route.Path, c) if assert.NotNil(t, h) { + for i, n := range c.pnames { + if assert.NotEmpty(t, n) { + assert.Equal(t, ":"+n, c.P(i)) + } + } h(c) } } diff --git a/website/docs/guide.md b/website/docs/guide.md index 73d0560ff..f6a694d9d 100644 --- a/website/docs/guide.md +++ b/website/docs/guide.md @@ -77,7 +77,7 @@ types of handlers. ### Path parameter Request path parameters can be extracted either by name `Echo.Context.Param(name string) string` -or by index `Echo.Context.P(i uint8) string`. Getting parameter by index gives a +or by index `Echo.Context.P(i int) string`. Getting parameter by index gives a slightly better performance. ```go