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
This commit is contained in:
Dominik Braun 2019-08-08 07:59:02 +02:00 committed by Matt Holt
parent c8b0a97b1c
commit 4950ce485f
6 changed files with 104 additions and 10 deletions

View file

@ -208,8 +208,16 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
// If no version information is available, a non-nil // If no version information is available, a non-nil
// value will still be returned, but with an // value will still be returned, but with an
// unknown version. // unknown version.
func GoModule() debug.Module { func GoModule() *debug.Module {
mod := debug.Module{Version: "unknown"} 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() bi, ok := debug.ReadBuildInfo()
if ok { if ok {
mod.Path = bi.Main.Path mod.Path = bi.Main.Path

View file

@ -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 // encode the response with encodingName. The returned response writer MUST
// be closed after the handler completes. // be closed after the handler completes.
func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter) *responseWriter { 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 := bufPool.Get().(*bytes.Buffer)
buf.Reset() buf.Reset()
return &responseWriter{
ResponseWriterWrapper: &caddyhttp.ResponseWriterWrapper{ResponseWriter: w}, // The allocation of ResponseWriterWrapper might be optimized as well.
encodingName: encodingName, rw.ResponseWriterWrapper = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW}
buf: buf, rw.encodingName = encodingName
config: enc, rw.buf = buf
} rw.config = enc
return rw
} }
// responseWriter writes to an underlying response writer // responseWriter writes to an underlying response writer

View file

@ -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)
}
}

View file

@ -133,14 +133,16 @@ func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Re
} }
func (fsrv *FileServer) browseWriteJSON(listing browseListing) (*bytes.Buffer, error) { 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) err := json.NewEncoder(buf).Encode(listing.Items)
bufPool.Put(buf)
return buf, err return buf, err
} }
func (fsrv *FileServer) browseWriteHTML(listing browseListing) (*bytes.Buffer, error) { 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) err := fsrv.Browse.template.Execute(buf, listing)
bufPool.Put(buf)
return buf, err return buf, err
} }

View file

@ -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)
}
}

View file

@ -15,6 +15,7 @@
package fileserver package fileserver
import ( import (
"bytes"
"fmt" "fmt"
"html/template" "html/template"
weakrand "math/rand" weakrand "math/rand"
@ -25,6 +26,7 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
@ -46,6 +48,7 @@ type FileServer struct {
Hide []string `json:"hide,omitempty"` Hide []string `json:"hide,omitempty"`
IndexNames []string `json:"index_names,omitempty"` IndexNames []string `json:"index_names,omitempty"`
Browse *Browse `json:"browse,omitempty"` Browse *Browse `json:"browse,omitempty"`
// TODO: Content negotiation // TODO: Content negotiation
} }
@ -301,6 +304,12 @@ func calculateEtag(d os.FileInfo) string {
var defaultIndexNames = []string{"index.html"} var defaultIndexNames = []string{"index.html"}
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
const minBackoff, maxBackoff = 2, 5 const minBackoff, maxBackoff = 2, 5
// Interface guards // Interface guards