From 4950ce485f7d931890fcfd2ee287b6df1b5db435 Mon Sep 17 00:00:00 2001 From: Dominik Braun Date: Thu, 8 Aug 2019 07:59:02 +0200 Subject: [PATCH] Part 1: Optimize using compiler's inliner (#2687) * optimized functions for inlining * added note regarding ResponseWriterWrapper * optimzed browseWrite* methods for FileServer * created benchmarks for comparison * creating browseListing instance in each function * created benchmarks for openResponseWriter * removed benchmarks of old implementations * implemented sync.Pool for byte buffers * using global sync.Pool for writing JSON/HTML --- caddy.go | 12 ++++- modules/caddyhttp/encode/encode.go | 21 +++++--- modules/caddyhttp/encode/encode_test.go | 12 +++++ modules/caddyhttp/fileserver/browse.go | 6 ++- modules/caddyhttp/fileserver/browse_test.go | 54 +++++++++++++++++++++ modules/caddyhttp/fileserver/staticfiles.go | 9 ++++ 6 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 modules/caddyhttp/encode/encode_test.go create mode 100644 modules/caddyhttp/fileserver/browse_test.go diff --git a/caddy.go b/caddy.go index 5a5349e9..c30eccf7 100644 --- a/caddy.go +++ b/caddy.go @@ -208,8 +208,16 @@ func (d *Duration) UnmarshalJSON(b []byte) error { // If no version information is available, a non-nil // value will still be returned, but with an // unknown version. -func GoModule() debug.Module { - mod := debug.Module{Version: "unknown"} +func GoModule() *debug.Module { + var mod debug.Module + return goModule(&mod) +} + +// goModule holds the actual implementation of GoModule. +// Allocating debug.Module in GoModule() and passing a +// reference to goModule enables mid-stack inlining. +func goModule(mod *debug.Module) *debug.Module { + mod.Version = "unknown" bi, ok := debug.ReadBuildInfo() if ok { mod.Path = bi.Main.Path diff --git a/modules/caddyhttp/encode/encode.go b/modules/caddyhttp/encode/encode.go index c78ccb90..b2c13271 100644 --- a/modules/caddyhttp/encode/encode.go +++ b/modules/caddyhttp/encode/encode.go @@ -93,14 +93,23 @@ func (enc *Encode) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyh // encode the response with encodingName. The returned response writer MUST // be closed after the handler completes. func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter) *responseWriter { + var rw responseWriter + return enc.initResponseWriter(&rw, encodingName, w) +} + +// initResponseWriter initializes the responseWriter instance +// allocated in openResponseWriter, enabling mid-stack inlining. +func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter) *responseWriter { buf := bufPool.Get().(*bytes.Buffer) buf.Reset() - return &responseWriter{ - ResponseWriterWrapper: &caddyhttp.ResponseWriterWrapper{ResponseWriter: w}, - encodingName: encodingName, - buf: buf, - config: enc, - } + + // The allocation of ResponseWriterWrapper might be optimized as well. + rw.ResponseWriterWrapper = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW} + rw.encodingName = encodingName + rw.buf = buf + rw.config = enc + + return rw } // responseWriter writes to an underlying response writer diff --git a/modules/caddyhttp/encode/encode_test.go b/modules/caddyhttp/encode/encode_test.go new file mode 100644 index 00000000..79eae3c5 --- /dev/null +++ b/modules/caddyhttp/encode/encode_test.go @@ -0,0 +1,12 @@ +package encode + +import ( + "testing" +) + +func BenchmarkOpenResponseWriter(b *testing.B) { + enc := new(Encode) + for n := 0; n < b.N; n++ { + enc.openResponseWriter("test", nil) + } +} diff --git a/modules/caddyhttp/fileserver/browse.go b/modules/caddyhttp/fileserver/browse.go index e0e6de2c..91ebcd5c 100644 --- a/modules/caddyhttp/fileserver/browse.go +++ b/modules/caddyhttp/fileserver/browse.go @@ -133,14 +133,16 @@ func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Re } func (fsrv *FileServer) browseWriteJSON(listing browseListing) (*bytes.Buffer, error) { - buf := new(bytes.Buffer) + buf := bufPool.Get().(*bytes.Buffer) err := json.NewEncoder(buf).Encode(listing.Items) + bufPool.Put(buf) return buf, err } func (fsrv *FileServer) browseWriteHTML(listing browseListing) (*bytes.Buffer, error) { - buf := new(bytes.Buffer) + buf := bufPool.Get().(*bytes.Buffer) err := fsrv.Browse.template.Execute(buf, listing) + bufPool.Put(buf) return buf, err } diff --git a/modules/caddyhttp/fileserver/browse_test.go b/modules/caddyhttp/fileserver/browse_test.go new file mode 100644 index 00000000..b1f7092b --- /dev/null +++ b/modules/caddyhttp/fileserver/browse_test.go @@ -0,0 +1,54 @@ +package fileserver + +import ( + "html/template" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +func BenchmarkBrowseWriteJSON(b *testing.B) { + fsrv := new(FileServer) + fsrv.Provision(caddy.Context{}) + listing := browseListing{ + Name: "test", + Path: "test", + CanGoUp: false, + Items: make([]fileInfo, 100), + NumDirs: 42, + NumFiles: 420, + Sort: "", + Order: "", + ItemsLimitedTo: 42, + } + b.ResetTimer() + + for n := 0; n < b.N; n++ { + fsrv.browseWriteJSON(listing) + } +} + +func BenchmarkBrowseWriteHTML(b *testing.B) { + fsrv := new(FileServer) + fsrv.Provision(caddy.Context{}) + fsrv.Browse = &Browse{ + TemplateFile: "", + template: template.New("test"), + } + listing := browseListing{ + Name: "test", + Path: "test", + CanGoUp: false, + Items: make([]fileInfo, 100), + NumDirs: 42, + NumFiles: 420, + Sort: "", + Order: "", + ItemsLimitedTo: 42, + } + b.ResetTimer() + + for n := 0; n < b.N; n++ { + fsrv.browseWriteHTML(listing) + } +} diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index 761dfc3c..a66b7532 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -15,6 +15,7 @@ package fileserver import ( + "bytes" "fmt" "html/template" weakrand "math/rand" @@ -25,6 +26,7 @@ import ( "path/filepath" "strconv" "strings" + "sync" "time" "github.com/caddyserver/caddy/v2" @@ -46,6 +48,7 @@ type FileServer struct { Hide []string `json:"hide,omitempty"` IndexNames []string `json:"index_names,omitempty"` Browse *Browse `json:"browse,omitempty"` + // TODO: Content negotiation } @@ -301,6 +304,12 @@ func calculateEtag(d os.FileInfo) string { var defaultIndexNames = []string{"index.html"} +var bufPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + const minBackoff, maxBackoff = 2, 5 // Interface guards