mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -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
					
				@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user