mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-06 22:40:31 -05:00
gzip: pool gzip.Writer to reduce allocation (#1618)
* gzip: add benchmark Signed-off-by: Tw <tw19881113@gmail.com> * gzip: pool gzip.Writer to reduce allocation Signed-off-by: Tw <tw19881113@gmail.com>
This commit is contained in:
parent
b18527285d
commit
cad89a07e0
3 changed files with 107 additions and 18 deletions
|
@ -4,9 +4,7 @@ package gzip
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
@ -22,6 +20,8 @@ func init() {
|
|||
ServerType: "http",
|
||||
Action: setup,
|
||||
})
|
||||
|
||||
initWriterPool()
|
||||
}
|
||||
|
||||
// Gzip is a middleware type which gzips HTTP responses. It is
|
||||
|
@ -58,12 +58,8 @@ outer:
|
|||
// gzipWriter modifies underlying writer at init,
|
||||
// use a discard writer instead to leave ResponseWriter in
|
||||
// original form.
|
||||
gzipWriter, err := newWriter(c, ioutil.Discard)
|
||||
if err != nil {
|
||||
// should not happen
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
defer gzipWriter.Close()
|
||||
gzipWriter := getWriter(c.Level)
|
||||
defer putWriter(c.Level, gzipWriter)
|
||||
gz := &gzipResponseWriter{Writer: gzipWriter, ResponseWriter: w}
|
||||
|
||||
var rw http.ResponseWriter
|
||||
|
@ -94,16 +90,6 @@ outer:
|
|||
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
|
||||
// with a gzip.Writer to compress the output.
|
||||
type gzipResponseWriter struct {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package gzip
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
@ -77,6 +78,22 @@ func TestGzipHandler(t *testing.T) {
|
|||
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 {
|
||||
|
@ -117,3 +134,37 @@ func nextFunc(shouldGzip bool) httpserver.Handler {
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package gzip
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mholt/caddy"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
|
@ -119,3 +122,52 @@ func gzipParse(c *caddy.Controller) ([]Config, error) {
|
|||
|
||||
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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue