diff --git a/caddy.go b/caddy.go index 5a5349e99..c30eccf77 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 c78ccb906..b2c132717 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 000000000..79eae3c56 --- /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 e0e6de2c2..91ebcd5c0 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 000000000..b1f7092bf --- /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 761dfc3cb..a66b75324 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