From a25c0557de9e1ff15a671d52f6af5fa7e0f58c92 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 16 Dec 2017 17:57:20 +0200 Subject: [PATCH] don't create a new controller instance if it doesn't have struct dependencies and the fields length is 0 - 0.4MB/s difference from the raw handlers now. Former-commit-id: f808291fe84bc2cdd83f60f62f8b3140204110a5 --- _benchmarks/iris-mvc/main.go | 4 +- mvc/controller.go | 127 ++++++++++++++++++++++++++++---- mvc/controller_method_parser.go | 2 +- mvc/param.go | 16 +--- 4 files changed, 117 insertions(+), 32 deletions(-) diff --git a/_benchmarks/iris-mvc/main.go b/_benchmarks/iris-mvc/main.go index 5b9ca837..77743dff 100644 --- a/_benchmarks/iris-mvc/main.go +++ b/_benchmarks/iris-mvc/main.go @@ -5,7 +5,7 @@ package main // with bindings or without). import ( - "github.com/kataras/iris/_benchmarks/iris-mvc2/controllers" + "github.com/kataras/iris/_benchmarks/iris-mvc/controllers" "github.com/kataras/iris" "github.com/kataras/iris/mvc" @@ -16,3 +16,5 @@ func main() { mvc.New(app.Party("/api/values/{id}")).Register(new(controllers.ValuesController)) app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) } + +// +2MB/s faster than the previous implementation, 0.4MB/s difference from the raw handlers. diff --git a/mvc/controller.go b/mvc/controller.go index 13556dc8..57d54dac 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -58,6 +58,8 @@ func getNameOf(typ reflect.Type) string { return fullname } +/// TODO: activate controllers with go routines so the startup time of iris +// can be improved on huge applications. func newControllerActivator(router router.Party, controller interface{}, d *di.D) *ControllerActivator { var ( val = reflect.ValueOf(controller) @@ -215,20 +217,125 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . if c.injector == nil { c.injector = c.Dependencies.Struct(c.Value) } + + handler := buildHandler(m, c.Type, c.Value, c.injector, funcInjector, funcIn) + + // register the handler now. + route := c.Router.Handle(method, path, append(middleware, handler)...) + if route != nil { + // change the main handler's name in order to respect the controller's and give + // a proper debug message. + route.MainHandlerName = fmt.Sprintf("%s.%s", c.FullName, funcName) + } + + return route +} + +// buildHandler has many many dublications but we do that to achieve the best +// performance possible, to use the information we know +// and calculate what is needed and what not in serve-time. +func buildHandler(m reflect.Method, typ reflect.Type, initRef reflect.Value, structInjector *di.StructInjector, funcInjector *di.FuncInjector, funcIn []reflect.Type) context.Handler { var ( - hasStructInjector = c.injector != nil && c.injector.Valid + hasStructInjector = structInjector != nil && structInjector.Valid hasFuncInjector = funcInjector != nil && funcInjector.Valid - implementsBase = isBaseController(c.Type) + implementsBase = isBaseController(typ) // we will make use of 'n' to make a slice of reflect.Value // to pass into if the function has input arguments that // are will being filled by the funcDependencies. n = len(funcIn) - elemTyp = di.IndirectType(c.Type) + elemTyp = di.IndirectType(typ) ) - handler := func(ctx context.Context) { + // if it doesn't implements the base controller, + // it may have struct injector and/or func injector. + if !implementsBase { + + if !hasStructInjector { + // if the controller doesn't have a struct injector + // and the controller's fields are empty + // then we don't need a new controller instance, we use the passed controller instance. + if elemTyp.NumField() == 0 { + if !hasFuncInjector { + return func(ctx context.Context) { + DispatchFuncResult(ctx, initRef.Method(m.Index).Call(emptyIn)) + } + } + + return func(ctx context.Context) { + in := make([]reflect.Value, n, n) + in[0] = initRef + funcInjector.Inject(&in, reflect.ValueOf(ctx)) + if ctx.IsStopped() { + return + } + + DispatchFuncResult(ctx, m.Func.Call(in)) + } + } + // it has fields, so it's request-scoped, even without struct injector + // it's safe to create a new controller on each request because the end-dev + // may use the controller's fields for request-scoping, so they should be + // zero on the next request. + if !hasFuncInjector { + return func(ctx context.Context) { + DispatchFuncResult(ctx, reflect.New(elemTyp).Method(m.Index).Call(emptyIn)) + } + } + return func(ctx context.Context) { + in := make([]reflect.Value, n, n) + in[0] = reflect.New(elemTyp) + funcInjector.Inject(&in, reflect.ValueOf(ctx)) + if ctx.IsStopped() { + return + } + + DispatchFuncResult(ctx, m.Func.Call(in)) + } + } + + // it has struct injector for sure and maybe a func injector. + if !hasFuncInjector { + return func(ctx context.Context) { + ctrl := reflect.New(elemTyp) + ctxValue := reflect.ValueOf(ctx) + elem := ctrl.Elem() + structInjector.InjectElem(elem, ctxValue) + if ctx.IsStopped() { + return + } + + DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) + } + } + + // has struct injector and func injector. + return func(ctx context.Context) { + ctrl := reflect.New(elemTyp) + ctxValue := reflect.ValueOf(ctx) + + elem := ctrl.Elem() + structInjector.InjectElem(elem, ctxValue) + if ctx.IsStopped() { + return + } + + in := make([]reflect.Value, n, n) + in[0] = ctrl + funcInjector.Inject(&in, ctxValue) + if ctx.IsStopped() { + return + } + + DispatchFuncResult(ctx, m.Func.Call(in)) + } + + } + + // if implements the base controller, + // it may have struct injector and func injector as well. + return func(ctx context.Context) { ctrl := reflect.New(elemTyp) if implementsBase { @@ -251,7 +358,7 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . ctxValue := reflect.ValueOf(ctx) if hasStructInjector { elem := ctrl.Elem() - c.injector.InjectElem(elem, ctxValue) + structInjector.InjectElem(elem, ctxValue) if ctx.IsStopped() { return } @@ -277,14 +384,4 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . } } - - // register the handler now. - route := c.Router.Handle(method, path, append(middleware, handler)...) - if route != nil { - // change the main handler's name in order to respect the controller's and give - // a proper debug message. - route.MainHandlerName = fmt.Sprintf("%s.%s", c.FullName, funcName) - } - - return route } diff --git a/mvc/controller_method_parser.go b/mvc/controller_method_parser.go index c5d0ec50..97c1282e 100644 --- a/mvc/controller_method_parser.go +++ b/mvc/controller_method_parser.go @@ -231,7 +231,7 @@ func (p *methodParser) parsePathParam(path string, w string, funcArgPos int) (st // so retry with the "funcArgPos" incremented. // // the "funcArgPos" will be updated to the caller as well - // because we return it as well. + // because we return it among the path and the error. return p.parsePathParam(path, w, funcArgPos+1) } return "", 0, errors.New("invalid syntax for " + p.fn.Name) diff --git a/mvc/param.go b/mvc/param.go index 4596afca..b66ae846 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -27,25 +27,11 @@ func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) // fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String()) if paramType.Assignable(in.Kind()) { consumedParams[j] = true - // fmt.Printf("path_param_binder.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String()) + // fmt.Printf("param.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String()) values = append(values, makeFuncParamGetter(paramType, paramName)) } } } - // funcInIdx := 0 - // // it's a valid param type. - // for _, p := range params { - // in := funcIn[funcInIdx] - // paramType := p.Type - // paramName := p.Name - // // fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String()) - // if paramType.Assignable(in.Kind()) { - // // fmt.Printf("path_param_binder.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String()) - // values = append(values, makeFuncParamGetter(paramType, paramName)) - // } - - // funcInIdx++ - // } return }