diff options
-rw-r--r-- | internal/vfs/zip/archive_test.go | 53 | ||||
-rw-r--r-- | internal/vfs/zip/deflate_reader.go | 43 |
2 files changed, 92 insertions, 4 deletions
diff --git a/internal/vfs/zip/archive_test.go b/internal/vfs/zip/archive_test.go index da778e62..58b7c74a 100644 --- a/internal/vfs/zip/archive_test.go +++ b/internal/vfs/zip/archive_test.go @@ -1,11 +1,16 @@ package zip import ( + "archive/zip" + "bytes" "context" + "crypto/rand" + "io" "io/ioutil" "net/http" "net/http/httptest" "os" + "strconv" "sync/atomic" "testing" "time" @@ -419,3 +424,51 @@ func newZipFileServerURL(t *testing.T, zipFilePath string, requests *int64) (str testServer.Close() } } + +func benchmarkArchiveRead(b *testing.B, size int64) { + zbuf := new(bytes.Buffer) + + // create zip file of specified size + zw := zip.NewWriter(zbuf) + w, err := zw.Create("public/file.txt") + require.NoError(b, err) + _, err = io.CopyN(w, rand.Reader, size) + require.NoError(b, err) + require.NoError(b, zw.Close()) + + modtime := time.Now().Add(-time.Hour) + + m := http.NewServeMux() + m.HandleFunc("/public.zip", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.ServeContent(w, r, "public.zip", modtime, bytes.NewReader(zbuf.Bytes())) + })) + + ts := httptest.NewServer(m) + defer ts.Close() + + fs := New(zipCfg).(*zipVFS) + + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + z := newArchive(fs, time.Second) + err := z.openArchive(context.Background(), ts.URL+"/public.zip") + require.NoError(b, err) + + f, err := z.Open(context.Background(), "file.txt") + require.NoError(b, err) + + _, err = io.Copy(ioutil.Discard, f) + require.NoError(b, err) + + require.NoError(b, f.Close()) + } +} + +func BenchmarkArchiveRead(b *testing.B) { + for _, size := range []int{32 * 1024, 64 * 1024, 1024 * 1024} { + b.Run(strconv.Itoa(size), func(b *testing.B) { + benchmarkArchiveRead(b, int64(size)) + }) + } +} diff --git a/internal/vfs/zip/deflate_reader.go b/internal/vfs/zip/deflate_reader.go index 16a2d72e..87a7da0c 100644 --- a/internal/vfs/zip/deflate_reader.go +++ b/internal/vfs/zip/deflate_reader.go @@ -1,31 +1,66 @@ package zip import ( + "bufio" "compress/flate" + "errors" "io" + "sync" ) +var ErrClosedReader = errors.New("deflatereader: reader is closed") + +var deflateReaderPool sync.Pool + // deflateReader wrapper to support reading compressed files. // Implements the io.ReadCloser interface. type deflateReader struct { - reader io.ReadCloser + reader *bufio.Reader + closer io.Closer flateReader io.ReadCloser } // Read from flateReader func (r *deflateReader) Read(p []byte) (n int, err error) { + if r.closer == nil { + return 0, ErrClosedReader + } + return r.flateReader.Read(p) } // Close all readers func (r *deflateReader) Close() error { - r.reader.Close() + if r.closer == nil { + return ErrClosedReader + } + + defer func() { + r.closer.Close() + r.closer = nil + deflateReaderPool.Put(r) + }() + return r.flateReader.Close() } +func (r *deflateReader) reset(rc io.ReadCloser) { + r.reader.Reset(rc) + r.closer = rc + r.flateReader.(flate.Resetter).Reset(r.reader, nil) +} + func newDeflateReader(r io.ReadCloser) *deflateReader { + if dr, ok := deflateReaderPool.Get().(*deflateReader); ok { + dr.reset(r) + return dr + } + + br := bufio.NewReader(r) + return &deflateReader{ - reader: r, - flateReader: flate.NewReader(r), + reader: br, + closer: r, + flateReader: flate.NewReader(br), } } |