diff --git a/internal/json/json.go b/internal/json/json.go index ee39349a..5b2ecee4 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -34,6 +34,7 @@ package json import ( "fmt" + "sync" ) type ( @@ -73,10 +74,31 @@ type ( } ) +var scannerPool = sync.Pool{ + New: func() any { + return &scanner{} + }, +} + +func newScanner() *scanner { + s := scannerPool.Get().(*scanner) + s.reset() + return s +} + +func freeScanner(s *scanner) { + // Avoid hanging on to too much memory in extreme cases. + if len(s.parseState) > 1024 { + s.parseState = nil + } + scannerPool.Put(s) +} + // Scan returns the number of bytes scanned and if there was any error // in trying to reach the end of data. func Scan(data []byte) (int, error) { - s := &scanner{} + s := newScanner() + defer freeScanner(s) _ = checkValid(data, s) return s.index, s.err } @@ -84,7 +106,6 @@ func Scan(data []byte) (int, error) { // checkValid verifies that data is valid JSON-encoded data. // scan is passed in for use by checkValid to avoid an allocation. func checkValid(data []byte, scan *scanner) error { - scan.reset() for _, c := range data { scan.index++ if scan.step(scan, c) == scanError { @@ -105,6 +126,8 @@ func (s *scanner) reset() { s.step = stateBeginValue s.parseState = s.parseState[0:0] s.err = nil + s.endTop = false + s.index = 0 } // eof tells the scanner that the end of input has been reached. diff --git a/mimetype_test.go b/mimetype_test.go index b76235bd..39d6c432 100644 --- a/mimetype_test.go +++ b/mimetype_test.go @@ -488,11 +488,9 @@ func BenchmarkSliceRand(b *testing.B) { b.ResetTimer() b.ReportAllocs() - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - Detect(data) - } - }) + for n := 0; n < b.N; n++ { + Detect(data) + } } func BenchmarkText(b *testing.B) { @@ -513,6 +511,30 @@ func BenchmarkText(b *testing.B) { } } +// BenchmarkFiles benchmarks each detector with his coresponding file. +func BenchmarkFiles(b *testing.B) { + for f, m := range files { + data, err := os.ReadFile(filepath.Join(testDataDir, f)) + if err != nil { + b.Fatal(err) + } + if uint32(len(data)) > defaultLimit { + data = data[:defaultLimit] + } + b.Run(f+"/"+m, func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + parsed, _, _ := mime.ParseMediaType(m) + mType := Lookup(parsed) + for n := 0; n < b.N; n++ { + if !mType.detector(data, uint32(len(data))) { + b.Fatal("detection should never fail") + } + } + }) + } +} + func BenchmarkAll(b *testing.B) { r := rand.New(rand.NewSource(0)) data := make([]byte, defaultLimit)