Commit cad89a07 authored by Tw's avatar Tw Committed by Matt Holt

gzip: pool gzip.Writer to reduce allocation (#1618)

* gzip: add benchmark
Signed-off-by: default avatarTw <tw19881113@gmail.com>

* gzip: pool gzip.Writer to reduce allocation
Signed-off-by: default avatarTw <tw19881113@gmail.com>
parent b1852728
...@@ -4,9 +4,7 @@ package gzip ...@@ -4,9 +4,7 @@ package gzip
import ( import (
"bufio" "bufio"
"compress/gzip"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"strings" "strings"
...@@ -22,6 +20,8 @@ func init() { ...@@ -22,6 +20,8 @@ func init() {
ServerType: "http", ServerType: "http",
Action: setup, Action: setup,
}) })
initWriterPool()
} }
// Gzip is a middleware type which gzips HTTP responses. It is // Gzip is a middleware type which gzips HTTP responses. It is
...@@ -58,12 +58,8 @@ outer: ...@@ -58,12 +58,8 @@ outer:
// gzipWriter modifies underlying writer at init, // gzipWriter modifies underlying writer at init,
// use a discard writer instead to leave ResponseWriter in // use a discard writer instead to leave ResponseWriter in
// original form. // original form.
gzipWriter, err := newWriter(c, ioutil.Discard) gzipWriter := getWriter(c.Level)
if err != nil { defer putWriter(c.Level, gzipWriter)
// should not happen
return http.StatusInternalServerError, err
}
defer gzipWriter.Close()
gz := &gzipResponseWriter{Writer: gzipWriter, ResponseWriter: w} gz := &gzipResponseWriter{Writer: gzipWriter, ResponseWriter: w}
var rw http.ResponseWriter var rw http.ResponseWriter
...@@ -94,16 +90,6 @@ outer: ...@@ -94,16 +90,6 @@ outer:
return g.Next.ServeHTTP(w, r) return g.Next.ServeHTTP(w, r)
} }
// newWriter create a new Gzip Writer based on the compression level.
// If the level is valid (i.e. between 1 and 9), it uses the level.
// Otherwise, it uses default compression level.
func newWriter(c Config, w io.Writer) (*gzip.Writer, error) {
if c.Level >= gzip.BestSpeed && c.Level <= gzip.BestCompression {
return gzip.NewWriterLevel(w, c.Level)
}
return gzip.NewWriter(w), nil
}
// gzipResponeWriter wraps the underlying Write method // gzipResponeWriter wraps the underlying Write method
// with a gzip.Writer to compress the output. // with a gzip.Writer to compress the output.
type gzipResponseWriter struct { type gzipResponseWriter struct {
......
package gzip package gzip
import ( import (
"compress/gzip"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
...@@ -77,6 +78,22 @@ func TestGzipHandler(t *testing.T) { ...@@ -77,6 +78,22 @@ func TestGzipHandler(t *testing.T) {
t.Error(err) t.Error(err)
} }
} }
// test all levels
w = httptest.NewRecorder()
gz.Next = nextFunc(true)
for i := 0; i <= gzip.BestCompression; i++ {
gz.Configs[0].Level = i
r, err := http.NewRequest("GET", "/file.txt", nil)
if err != nil {
t.Error(err)
}
r.Header.Set("Accept-Encoding", "gzip")
_, err = gz.ServeHTTP(w, r)
if err != nil {
t.Error(err)
}
}
} }
func nextFunc(shouldGzip bool) httpserver.Handler { func nextFunc(shouldGzip bool) httpserver.Handler {
...@@ -117,3 +134,37 @@ func nextFunc(shouldGzip bool) httpserver.Handler { ...@@ -117,3 +134,37 @@ func nextFunc(shouldGzip bool) httpserver.Handler {
return 0, nil return 0, nil
}) })
} }
func BenchmarkGzip(b *testing.B) {
pathFilter := PathFilter{make(Set)}
badPaths := []string{"/bad", "/nogzip", "/nongzip"}
for _, p := range badPaths {
pathFilter.IgnoredPaths.Add(p)
}
extFilter := ExtFilter{make(Set)}
for _, e := range []string{".txt", ".html", ".css", ".md"} {
extFilter.Exts.Add(e)
}
gz := Gzip{Configs: []Config{
{
RequestFilters: []RequestFilter{pathFilter, extFilter},
},
}}
w := httptest.NewRecorder()
gz.Next = nextFunc(true)
url := "/file.txt"
r, err := http.NewRequest("GET", url, nil)
if err != nil {
b.Fatal(err)
}
r.Header.Set("Accept-Encoding", "gzip")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err = gz.ServeHTTP(w, r)
if err != nil {
b.Fatal(err)
}
}
}
package gzip package gzip
import ( import (
"compress/gzip"
"fmt" "fmt"
"io/ioutil"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver" "github.com/mholt/caddy/caddyhttp/httpserver"
...@@ -119,3 +122,52 @@ func gzipParse(c *caddy.Controller) ([]Config, error) { ...@@ -119,3 +122,52 @@ func gzipParse(c *caddy.Controller) ([]Config, error) {
return configs, nil return configs, nil
} }
// pool gzip.Writer according to compress level
// so we can reuse allocations over time
var (
writerPool = map[int]*sync.Pool{}
defaultWriterPoolIndex int
)
func initWriterPool() {
var i int
newWriterPool := func(level int) *sync.Pool {
return &sync.Pool{
New: func() interface{} {
w, _ := gzip.NewWriterLevel(ioutil.Discard, level)
return w
},
}
}
for i = gzip.BestSpeed; i <= gzip.BestCompression; i++ {
writerPool[i] = newWriterPool(i)
}
// add default writer pool
defaultWriterPoolIndex = i
writerPool[defaultWriterPoolIndex] = &sync.Pool{
New: func() interface{} {
return gzip.NewWriter(ioutil.Discard)
},
}
}
func getWriter(level int) *gzip.Writer {
index := defaultWriterPoolIndex
if level >= gzip.BestSpeed && level <= gzip.BestCompression {
index = level
}
w := writerPool[index].Get().(*gzip.Writer)
w.Reset(ioutil.Discard)
return w
}
func putWriter(level int, w *gzip.Writer) {
index := defaultWriterPoolIndex
if level >= gzip.BestSpeed && level <= gzip.BestCompression {
index = level
}
w.Close()
writerPool[index].Put(w)
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment