From 9a00eef290ba0342a210eeacf09f496361806d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Fri, 16 Jul 2021 14:16:23 +0200 Subject: [PATCH] exif: Improve performance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, do not return an error when input format isn't supported. Just return `nil` exif. Using the stream API and avoid creating the Ifd mapping and tag index every time makes it faster: ``` name old time/op new time/op delta DecodeExif-16 3.42ms ± 0% 0.65ms ± 0% -81.12% (p=0.029 n=4+4) name old alloc/op new alloc/op delta DecodeExif-16 1.51MB ± 0% 0.59MB ± 0% -60.77% (p=0.029 n=4+4) name old allocs/op new allocs/op delta DecodeExif-16 12.9k ± 0% 1.9k ± 0% -85.48% (p=0.029 n=4+4) ``` It's still slower than what we had, though, but correctness comes at a cost: ``` name old time/op new time/op delta DecodeExif-16 186µs ± 0% 648µs ± 1% +249.21% (p=0.029 n=4+4) name old alloc/op new alloc/op delta DecodeExif-16 184kB ± 0% 593kB ± 0% +222.38% (p=0.029 n=4+4) name old allocs/op new allocs/op delta DecodeExif-16 1.20k ± 0% 1.88k ± 0% +55.99% (p=0.029 n=4+4) ``` See #8586 --- go.mod | 3 ++ resources/images/exif/exif.go | 31 ++++++++------------- resources/images/exif/exif_test.go | 44 ++++++++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index 57d32a81b5d..abe6fad09f7 100644 --- a/go.mod +++ b/go.mod @@ -72,3 +72,6 @@ require ( ) go 1.16 + + +replace github.com/dsoprea/go-exif/v3 => /Users/bep/dev/go/dump/go-exif/v3 \ No newline at end of file diff --git a/resources/images/exif/exif.go b/resources/images/exif/exif.go index 929c25bd378..c08bfb1c745 100644 --- a/resources/images/exif/exif.go +++ b/resources/images/exif/exif.go @@ -14,9 +14,7 @@ package exif import ( - "fmt" "io" - "io/ioutil" "math/big" "regexp" "strings" @@ -26,7 +24,6 @@ import ( _exif "github.com/dsoprea/go-exif/v3" exifcommon "github.com/dsoprea/go-exif/v3/common" - png "github.com/dsoprea/go-png-image-structure" ) const ( @@ -47,6 +44,9 @@ type Decoder struct { excludeFieldsrRe *regexp.Regexp noDate bool noLatLong bool + + idfm *exifcommon.IfdMapping + ti *_exif.TagIndex } func IncludeFields(expression string) func(*Decoder) error { @@ -99,7 +99,12 @@ func compileRegexp(expression string) (*regexp.Regexp, error) { } func NewDecoder(options ...func(*Decoder) error) (*Decoder, error) { - d := &Decoder{} + im, err := exifcommon.NewIfdMappingWithStandard() + if err != nil { + return nil, err + } + ti := _exif.NewTagIndex() + d := &Decoder{idfm: im, ti: ti} for _, opt := range options { if err := opt(d); err != nil { return nil, err @@ -110,28 +115,14 @@ func NewDecoder(options ...func(*Decoder) error) (*Decoder, error) { } func (d *Decoder) Decode(r io.Reader) (*Exif, error) { - rawBytes, err := ioutil.ReadAll(r) - if err != nil { - return nil, err - } - rawExif, err := _exif.SearchAndExtractExif(rawBytes) + rawExif, err := _exif.SearchAndExtractExifWithReader(r) if err != nil { - parsed := png.NewPngMediaParser() - if parsed.LooksLikeFormat(rawBytes) { - return nil, fmt.Errorf("type not supported") - } // No Exif data return nil, nil } - im, err := exifcommon.NewIfdMappingWithStandard() - if err != nil { - return nil, err - } - ti := _exif.NewTagIndex() - - _, index, err := _exif.Collect(im, ti, rawExif) + _, index, err := _exif.Collect(d.idfm, d.ti, rawExif) if err != nil { return nil, err } diff --git a/resources/images/exif/exif_test.go b/resources/images/exif/exif_test.go index 555d28c3045..d6c763a07f9 100644 --- a/resources/images/exif/exif_test.go +++ b/resources/images/exif/exif_test.go @@ -14,13 +14,16 @@ package exif import ( + "context" "encoding/json" + "errors" "math/big" "os" "path/filepath" "testing" "time" + "github.com/gohugoio/hugo/common/para" "github.com/gohugoio/hugo/htesting/hqt" "github.com/google/go-cmp/cmp" @@ -62,6 +65,42 @@ func TestExif(t *testing.T) { c.Assert(x2, eq, x) } +func TestExifParallel(t *testing.T) { + c := qt.New(t) + + d, err := NewDecoder(IncludeFields("Lens|Date")) + c.Assert(err, qt.IsNil) + + p := para.New(4) + r, _ := p.Start(context.Background()) + + for i := 0; i < 20; i++ { + r.Run(func() error { + f, err := os.Open(filepath.FromSlash("../../testdata/sunset.jpg")) + if err != nil { + return err + } + defer f.Close() + + x, err := d.Decode(f) + if err != nil { + return err + } + + if x.Date.Format("2006-01-02") != "2017-10-27" { + return errors.New("invalid date") + } + + return nil + + }) + + } + + c.Assert(r.Wait(), qt.IsNil) + +} + func TestImageWithoutExifData(t *testing.T) { c := qt.New(t) f, err := os.Open(filepath.FromSlash("../../testdata/sunset_without_exif.jpg")) @@ -143,8 +182,9 @@ func TestExifPNG(t *testing.T) { d, err := NewDecoder() c.Assert(err, qt.IsNil) - _, err = d.Decode(f) - c.Assert(err, qt.Not(qt.IsNil)) + x, err := d.Decode(f) + c.Assert(err, qt.IsNil) + c.Assert(x, qt.IsNil) } func TestIssue8079(t *testing.T) {