From 10979dd8627c2a91d729fbb65cdb91e032cb417b Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Wed, 2 Jul 2014 20:17:57 +0200 Subject: [PATCH 1/5] Adds new context caching system - Reduces allocation overhead - Reduces Garbage Collection stress - Reduces memory fragmentation --- gin.go | 49 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/gin.go b/gin.go index 847df19c..91ebcae7 100644 --- a/gin.go +++ b/gin.go @@ -56,9 +56,10 @@ type ( // Represents the web framework, it wrappers the blazing fast httprouter multiplexer and a list of global middlewares. Engine struct { *RouterGroup + HTMLTemplates *template.Template + cache chan *Context handlers404 []HandlerFunc router *httprouter.Router - HTMLTemplates *template.Template } ) @@ -74,10 +75,18 @@ func (a ErrorMsgs) String() string { // Returns a new blank Engine instance without any middleware attached. // The most basic configuration func New() *Engine { + cacheSize := 1024 + engine := &Engine{} engine.RouterGroup = &RouterGroup{nil, "/", nil, engine} engine.router = httprouter.New() engine.router.NotFound = engine.handle404 + engine.cache = make(chan *Context, cacheSize) + + // Fill it with empty contexts + for i := 0; i < cacheSize/2; i++ { + engine.cache <- &Context{engine: engine} + } return engine } @@ -107,6 +116,7 @@ func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) { } c.Next() + engine.reuseContext(c) } // ServeFiles serves files from the given file system root. @@ -136,14 +146,31 @@ func (engine *Engine) Run(addr string) { /********** ROUTES GROUPING *********/ /************************************/ -func (group *RouterGroup) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context { - return &Context{ - Writer: w, - Req: req, - index: -1, - engine: group.engine, - Params: params, - handlers: handlers, +func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context { + select { + case c := <-engine.cache: + c.Writer = w + c.Req = req + c.Params = params + c.handlers = handlers + c.index = -1 + return c + default: + return &Context{ + Writer: w, + Req: req, + Params: params, + handlers: handlers, + index: -1, + engine: engine, + } + } +} + +func (engine *Engine) reuseContext(c *Context) { + select { + case engine.cache <- c: + default: } } @@ -178,7 +205,9 @@ func (group *RouterGroup) Handle(method, p string, handlers []HandlerFunc) { p = path.Join(group.prefix, p) handlers = group.combineHandlers(handlers) group.engine.router.Handle(method, p, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { - group.createContext(w, req, params, handlers).Next() + c := group.engine.createContext(w, req, params, handlers) + c.Next() + group.engine.reuseContext(c) }) } From 30ea9c06fc9cac893f10d6ad0847a510cf35aeaf Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Thu, 3 Jul 2014 14:11:42 +0200 Subject: [PATCH 2/5] Adds NewWithConfig() and CacheStress() --- gin.go | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/gin.go b/gin.go index 91ebcae7..f60901cd 100644 --- a/gin.go +++ b/gin.go @@ -31,6 +31,11 @@ type ( ErrorMsgs []ErrorMsg + Config struct { + CacheSize int + Preallocated int + } + // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. Context struct { @@ -39,8 +44,8 @@ type ( Keys map[string]interface{} Errors ErrorMsgs Params httprouter.Params + Engine *Engine handlers []HandlerFunc - engine *Engine index int8 } @@ -72,24 +77,35 @@ func (a ErrorMsgs) String() string { return buffer.String() } -// Returns a new blank Engine instance without any middleware attached. -// The most basic configuration -func New() *Engine { - cacheSize := 1024 - +func NewWithConfig(config Config) *Engine { + if config.CacheSize < 2 { + panic("CacheSize must be at least 2") + } + if config.Preallocated > config.CacheSize { + panic("Preallocated must be less or equal to CacheSize") + } engine := &Engine{} engine.RouterGroup = &RouterGroup{nil, "/", nil, engine} engine.router = httprouter.New() engine.router.NotFound = engine.handle404 - engine.cache = make(chan *Context, cacheSize) + engine.cache = make(chan *Context, config.CacheSize) // Fill it with empty contexts - for i := 0; i < cacheSize/2; i++ { - engine.cache <- &Context{engine: engine} + for i := 0; i < config.Preallocated; i++ { + engine.cache <- &Context{Engine: engine} } return engine } +// Returns a new blank Engine instance without any middleware attached. +// The most basic configuration +func New() *Engine { + return NewWithConfig(Config{ + CacheSize: 1024, + Preallocated: 512, + }) +} + // Returns a Engine instance with the Logger and Recovery already attached. func Default() *Engine { engine := New() @@ -106,6 +122,10 @@ func (engine *Engine) NotFound404(handlers ...HandlerFunc) { engine.handlers404 = handlers } +func (engine *Engine) CacheStress() float32 { + return 1.0 - float32(len(engine.cache))/float32(cap(engine.cache)) +} + func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) { handlers := engine.combineHandlers(engine.handlers404) c := engine.createContext(w, req, nil, handlers) @@ -162,7 +182,7 @@ func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, pa Params: params, handlers: handlers, index: -1, - engine: engine, + Engine: engine, } } } @@ -385,7 +405,7 @@ func (c *Context) HTML(code int, name string, data interface{}) { if code >= 0 { c.Writer.WriteHeader(code) } - if err := c.engine.HTMLTemplates.ExecuteTemplate(c.Writer, name, data); err != nil { + if err := c.Engine.HTMLTemplates.ExecuteTemplate(c.Writer, name, data); err != nil { c.Error(err, map[string]interface{}{ "name": name, "data": data, From d9573b45c71db971477d5eea9189497c1a7b2a20 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Thu, 3 Jul 2014 15:59:39 +0200 Subject: [PATCH 3/5] Adds Keep() and Release() to gin.Context --- gin.go | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/gin.go b/gin.go index f60901cd..fea800f2 100644 --- a/gin.go +++ b/gin.go @@ -45,6 +45,7 @@ type ( Errors ErrorMsgs Params httprouter.Params Engine *Engine + keep bool handlers []HandlerFunc index int8 } @@ -180,6 +181,7 @@ func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, pa Writer: w, Req: req, Params: params, + keep: false, handlers: handlers, index: -1, Engine: engine, @@ -188,9 +190,11 @@ func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, pa } func (engine *Engine) reuseContext(c *Context) { - select { - case engine.cache <- c: - default: + if c.keep == false { + select { + case engine.cache <- c: + default: + } } } @@ -268,6 +272,23 @@ func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc /****** FLOW AND ERROR MANAGEMENT****/ /************************************/ +func (c *Context) Keep() { + if c.keep == false { + c.keep = true + } else { + log.Println("gin: trying to Keep same context several times") + } +} + +func (c *Context) Release() { + if c.keep == true { + c.keep = false + c.Engine.reuseContext(c) + } else { + log.Println("gin: bug: trying to Release same context several times") + } +} + // Next should be used only in the middlewares. // It executes the pending handlers in the chain inside the calling handler. // See example in github. From d7a3fdcd8fa5a38111578c254a38616e6af5a76c Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Thu, 3 Jul 2014 16:31:27 +0200 Subject: [PATCH 4/5] Testing Copy() instead of Keep() and Release() --- gin.go | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/gin.go b/gin.go index fea800f2..ffc95818 100644 --- a/gin.go +++ b/gin.go @@ -45,7 +45,6 @@ type ( Errors ErrorMsgs Params httprouter.Params Engine *Engine - keep bool handlers []HandlerFunc index int8 } @@ -174,6 +173,7 @@ func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, pa c.Req = req c.Params = params c.handlers = handlers + c.Keys = nil c.index = -1 return c default: @@ -181,7 +181,6 @@ func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, pa Writer: w, Req: req, Params: params, - keep: false, handlers: handlers, index: -1, Engine: engine, @@ -190,11 +189,9 @@ func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, pa } func (engine *Engine) reuseContext(c *Context) { - if c.keep == false { - select { - case engine.cache <- c: - default: - } + select { + case engine.cache <- c: + default: } } @@ -272,21 +269,12 @@ func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc /****** FLOW AND ERROR MANAGEMENT****/ /************************************/ -func (c *Context) Keep() { - if c.keep == false { - c.keep = true - } else { - log.Println("gin: trying to Keep same context several times") - } -} - -func (c *Context) Release() { - if c.keep == true { - c.keep = false - c.Engine.reuseContext(c) - } else { - log.Println("gin: bug: trying to Release same context several times") - } +func (c *Context) Copy() *Context { + cp := &Context{} + *cp = *c + cp.index = AbortIndex + cp.handlers = nil + return cp } // Next should be used only in the middlewares. From 12fa9fe4993674bd64a3fcbcb298b6e7741aedb5 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Thu, 3 Jul 2014 16:35:20 +0200 Subject: [PATCH 5/5] Improves Copy() in gin.Context --- gin.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gin.go b/gin.go index ffc95818..3c8e49e9 100644 --- a/gin.go +++ b/gin.go @@ -270,11 +270,10 @@ func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc /************************************/ func (c *Context) Copy() *Context { - cp := &Context{} - *cp = *c + var cp Context = *c cp.index = AbortIndex cp.handlers = nil - return cp + return &cp } // Next should be used only in the middlewares.