From f79e90bad17cb06a14a813f622f9f123004a5644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 7 Sep 2021 15:43:09 +0200 Subject: [PATCH 1/2] avoid allocating on every byte read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need a ByteReader for some APIs, such as reading CIDs. However, our entrypoints often don't, such as Reader or ReaderAt. Thus, we have a type that does the wrapping to support ReadByte. We were converting a non-pointer to an interface, which forcibly allocates, since interfaces must contain pointers. Fix that by making the ReadByte methods use pointer receivers. name old time/op new time/op delta ReadBlocks-16 1.18ms ± 2% 1.15ms ± 5% -2.73% (p=0.003 n=11+11) name old speed new speed delta ReadBlocks-16 441MB/s ± 2% 453MB/s ± 5% +2.85% (p=0.003 n=11+11) name old alloc/op new alloc/op delta ReadBlocks-16 1.33MB ± 0% 1.29MB ± 0% -2.41% (p=0.000 n=12+12) name old allocs/op new allocs/op delta ReadBlocks-16 13.5k ± 0% 11.5k ± 0% -14.79% (p=0.000 n=12+12) --- v2/internal/io/converter.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v2/internal/io/converter.go b/v2/internal/io/converter.go index 65350d34..5435471f 100644 --- a/v2/internal/io/converter.go +++ b/v2/internal/io/converter.go @@ -63,11 +63,11 @@ func ToReaderAt(rs io.ReadSeeker) io.ReaderAt { return &readSeekerAt{rs: rs} } -func (rb readerPlusByte) ReadByte() (byte, error) { +func (rb *readerPlusByte) ReadByte() (byte, error) { return readByte(rb) } -func (rsb readSeekerPlusByte) ReadByte() (byte, error) { +func (rsb *readSeekerPlusByte) ReadByte() (byte, error) { return readByte(rsb) } From 3c1c62a364c26ed3b2d1c0eee3f095503bbaf496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 7 Sep 2021 16:31:55 +0200 Subject: [PATCH 2/2] avoid another alloc per read byte MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Like the last commit, avoid an extra allocation per read byte. In this case, we created a one-byte buffer for each readByte call. Unfortunately, since io.Reader is an interface, the compiler can't know if it holds onto the memory, so the buffer escapes and cannot be placed in the stack. To sidestep this issue, reuse a preallocated buffer. We know this is fine, because we only do sequential reads. name old time/op new time/op delta ReadBlocks-16 1.15ms ± 5% 1.09ms ± 4% -5.13% (p=0.000 n=11+11) name old speed new speed delta ReadBlocks-16 453MB/s ± 5% 478MB/s ± 4% +5.41% (p=0.000 n=11+11) name old alloc/op new alloc/op delta ReadBlocks-16 1.29MB ± 0% 1.30MB ± 0% +0.48% (p=0.000 n=12+12) name old allocs/op new allocs/op delta ReadBlocks-16 11.5k ± 0% 9.5k ± 0% -17.35% (p=0.000 n=12+12) --- v2/internal/io/converter.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/v2/internal/io/converter.go b/v2/internal/io/converter.go index 5435471f..21011b6e 100644 --- a/v2/internal/io/converter.go +++ b/v2/internal/io/converter.go @@ -17,15 +17,21 @@ var ( type ( readerPlusByte struct { io.Reader + + byteBuf [1]byte // escapes via io.Reader.Read; preallocate } readSeekerPlusByte struct { io.ReadSeeker + + byteBuf [1]byte // escapes via io.Reader.Read; preallocate } discardingReadSeekerPlusByte struct { io.Reader offset int64 + + byteBuf [1]byte // escapes via io.Reader.Read; preallocate } ByteReadSeeker interface { @@ -43,7 +49,7 @@ func ToByteReader(r io.Reader) io.ByteReader { if br, ok := r.(io.ByteReader); ok { return br } - return &readerPlusByte{r} + return &readerPlusByte{Reader: r} } func ToByteReadSeeker(r io.Reader) ByteReadSeeker { @@ -51,7 +57,7 @@ func ToByteReadSeeker(r io.Reader) ByteReadSeeker { return brs } if rs, ok := r.(io.ReadSeeker); ok { - return &readSeekerPlusByte{rs} + return &readSeekerPlusByte{ReadSeeker: rs} } return &discardingReadSeekerPlusByte{Reader: r} } @@ -64,15 +70,18 @@ func ToReaderAt(rs io.ReadSeeker) io.ReaderAt { } func (rb *readerPlusByte) ReadByte() (byte, error) { - return readByte(rb) + _, err := io.ReadFull(rb, rb.byteBuf[:]) + return rb.byteBuf[0], err } func (rsb *readSeekerPlusByte) ReadByte() (byte, error) { - return readByte(rsb) + _, err := io.ReadFull(rsb, rsb.byteBuf[:]) + return rsb.byteBuf[0], err } func (drsb *discardingReadSeekerPlusByte) ReadByte() (byte, error) { - return readByte(drsb) + _, err := io.ReadFull(drsb, drsb.byteBuf[:]) + return drsb.byteBuf[0], err } func (drsb *discardingReadSeekerPlusByte) Read(p []byte) (read int, err error) { @@ -98,12 +107,6 @@ func (drsb *discardingReadSeekerPlusByte) Seek(offset int64, whence int) (int64, } } -func readByte(r io.Reader) (byte, error) { - var p [1]byte - _, err := io.ReadFull(r, p[:]) - return p[0], err -} - func (rsa *readSeekerAt) ReadAt(p []byte, off int64) (n int, err error) { rsa.mu.Lock() defer rsa.mu.Unlock()