Skip to content

Commit

Permalink
perf: reduce NewCarReader allocations
Browse files Browse the repository at this point in the history
This accounted for almost 10% of the garbage on one of Bluesky's
server daemons (go tool pprof --alloc_space), leading to high GC CPU.

benchstat:

                         │   before    │                after                │
                         │   sec/op    │   sec/op     vs base                │
    NewCarReader_small-8   2.005µ ± 1%   1.451µ ± 5%  -27.61% (p=0.000 n=10)

                         │    before    │                after                 │
                         │     B/op     │     B/op      vs base                │
    NewCarReader_small-8   5.268Ki ± 0%   1.135Ki ± 0%  -78.46% (p=0.000 n=10)

                         │   before   │               after               │
                         │ allocs/op  │ allocs/op   vs base               │
    NewCarReader_small-8   29.00 ± 0%   27.00 ± 0%  -6.90% (p=0.000 n=10)
  • Loading branch information
bradfitz authored and rvagg committed May 9, 2023
1 parent 13b269b commit 92d28eb
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 2 deletions.
20 changes: 18 additions & 2 deletions car.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"fmt"
"io"
"sync"

blocks "github.com/ipfs/go-block-format"
cid "github.com/ipfs/go-cid"
Expand Down Expand Up @@ -118,15 +119,21 @@ func (cw *carWriter) writeNode(ctx context.Context, nd format.Node) error {
return util.LdWrite(cw.w, nd.Cid().Bytes(), nd.RawData())
}

var bufioReaderPool = sync.Pool{
New: func() any { return bufio.NewReader(nil) },
}

type CarReader struct {
br *bufio.Reader
br *bufio.Reader // set nil on EOF
Header *CarHeader
}

func NewCarReader(r io.Reader) (*CarReader, error) {
br := bufio.NewReader(r)
br := bufioReaderPool.Get().(*bufio.Reader)
br.Reset(r)
ch, err := ReadHeader(br)
if err != nil {
bufioReaderPool.Put(br)
return nil, err
}

Expand All @@ -145,8 +152,17 @@ func NewCarReader(r io.Reader) (*CarReader, error) {
}

func (cr *CarReader) Next() (blocks.Block, error) {
if cr.br == nil {
return nil, io.EOF
}
c, data, err := util.ReadNode(cr.br)
if err != nil {
if err == io.EOF {
// Common happy case: recycle the bufio.Reader.
// In the other error paths leaking it is fine.
bufioReaderPool.Put(cr.br)
cr.br = nil
}
return nil, err
}

Expand Down
24 changes: 24 additions & 0 deletions car_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,27 @@ func TestBadHeaders(t *testing.T) {
})
}
}

func BenchmarkNewCarReader_small(b *testing.B) {
b.ReportAllocs()

fixture, err := hex.DecodeString(fixtureStr)
if err != nil {
b.Fatal(err)
}
for i := 0; i < b.N; i++ {
cr, err := car.NewCarReader(bytes.NewReader(fixture))
if err != nil {
b.Fatal(err)
}
for {
_, err := cr.Next()
if err == io.EOF {
break
}
if err != nil {
b.Fatal(err)
}
}
}
}

0 comments on commit 92d28eb

Please sign in to comment.