mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-19 01:05:37 +03: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 (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"compress/gzip"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -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:
|
||||||
// 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:
|
||||||
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 {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
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) {
|
||||||
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 {
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
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) {
|
||||||
|
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue