mirror of
https://github.com/golang/go
synced 2024-11-26 10:18:12 -07:00
bufio: make Reader buffer transient
Share garbage between different bufio Readers. When a Reader has zero buffered data, put its buffer into a pool. This acknowledges that most bufio.Readers eventually get read to completion, and their buffers are then no longer needed. benchmark old ns/op new ns/op delta BenchmarkReaderEmpty 2993 1058 -64.65% benchmark old allocs new allocs delta BenchmarkReaderEmpty 3 2 -33.33% benchmark old bytes new bytes delta BenchmarkReaderEmpty 4278 133 -96.89% Update #5100 R=r CC=adg, dvyukov, gobot, golang-dev, rogpeppe https://golang.org/cl/8819049
This commit is contained in:
parent
249842c8db
commit
b25a53acd7
@ -29,7 +29,8 @@ var (
|
|||||||
|
|
||||||
// Reader implements buffering for an io.Reader object.
|
// Reader implements buffering for an io.Reader object.
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
buf []byte
|
buf []byte // either nil or []byte of size bufSize
|
||||||
|
bufSize int
|
||||||
rd io.Reader
|
rd io.Reader
|
||||||
r, w int
|
r, w int
|
||||||
err error
|
err error
|
||||||
@ -45,18 +46,23 @@ const minReadBufferSize = 16
|
|||||||
func NewReaderSize(rd io.Reader, size int) *Reader {
|
func NewReaderSize(rd io.Reader, size int) *Reader {
|
||||||
// Is it already a Reader?
|
// Is it already a Reader?
|
||||||
b, ok := rd.(*Reader)
|
b, ok := rd.(*Reader)
|
||||||
if ok && len(b.buf) >= size {
|
if ok && b.bufSize >= size {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
if size < minReadBufferSize {
|
if size < minReadBufferSize {
|
||||||
size = minReadBufferSize
|
size = minReadBufferSize
|
||||||
}
|
}
|
||||||
return &Reader{
|
r := &Reader{
|
||||||
buf: make([]byte, size),
|
bufSize: size,
|
||||||
rd: rd,
|
rd: rd,
|
||||||
lastByte: -1,
|
lastByte: -1,
|
||||||
lastRuneSize: -1,
|
lastRuneSize: -1,
|
||||||
}
|
}
|
||||||
|
if size > defaultBufSize {
|
||||||
|
// TODO(bradfitz): make all buffer sizes recycle
|
||||||
|
r.buf = make([]byte, r.bufSize)
|
||||||
|
}
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReader returns a new Reader whose buffer has the default size.
|
// NewReader returns a new Reader whose buffer has the default size.
|
||||||
@ -66,8 +72,42 @@ func NewReader(rd io.Reader) *Reader {
|
|||||||
|
|
||||||
var errNegativeRead = errors.New("bufio: reader returned negative count from Read")
|
var errNegativeRead = errors.New("bufio: reader returned negative count from Read")
|
||||||
|
|
||||||
|
// TODO: use a sync.Cache instead of this:
|
||||||
|
const arbitrarySize = 8
|
||||||
|
|
||||||
|
// bufCache holds only byte slices with capacity defaultBufSize.
|
||||||
|
var bufCache = make(chan []byte, arbitrarySize)
|
||||||
|
|
||||||
|
// allocBuf makes b.buf non-nil.
|
||||||
|
func (b *Reader) allocBuf() {
|
||||||
|
if b.buf != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case b.buf = <-bufCache:
|
||||||
|
b.buf = b.buf[:b.bufSize]
|
||||||
|
default:
|
||||||
|
b.buf = make([]byte, b.bufSize, defaultBufSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// putBuf returns b.buf if it's unused.
|
||||||
|
func (b *Reader) putBuf() {
|
||||||
|
if b.r == b.w && b.err == io.EOF && cap(b.buf) == defaultBufSize {
|
||||||
|
select {
|
||||||
|
case bufCache <- b.buf:
|
||||||
|
b.buf = nil
|
||||||
|
b.r = 0
|
||||||
|
b.w = 0
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// fill reads a new chunk into the buffer.
|
// fill reads a new chunk into the buffer.
|
||||||
func (b *Reader) fill() {
|
func (b *Reader) fill() {
|
||||||
|
b.allocBuf()
|
||||||
|
|
||||||
// Slide existing data to beginning.
|
// Slide existing data to beginning.
|
||||||
if b.r > 0 {
|
if b.r > 0 {
|
||||||
copy(b.buf, b.buf[b.r:b.w])
|
copy(b.buf, b.buf[b.r:b.w])
|
||||||
@ -100,7 +140,7 @@ func (b *Reader) Peek(n int) ([]byte, error) {
|
|||||||
if n < 0 {
|
if n < 0 {
|
||||||
return nil, ErrNegativeCount
|
return nil, ErrNegativeCount
|
||||||
}
|
}
|
||||||
if n > len(b.buf) {
|
if n > b.bufSize {
|
||||||
return nil, ErrBufferFull
|
return nil, ErrBufferFull
|
||||||
}
|
}
|
||||||
for b.w-b.r < n && b.err == nil {
|
for b.w-b.r < n && b.err == nil {
|
||||||
@ -134,7 +174,7 @@ func (b *Reader) Read(p []byte) (n int, err error) {
|
|||||||
if b.err != nil {
|
if b.err != nil {
|
||||||
return 0, b.readErr()
|
return 0, b.readErr()
|
||||||
}
|
}
|
||||||
if len(p) >= len(b.buf) {
|
if len(p) >= b.bufSize {
|
||||||
// Large read, empty buffer.
|
// Large read, empty buffer.
|
||||||
// Read directly into p to avoid copy.
|
// Read directly into p to avoid copy.
|
||||||
n, b.err = b.rd.Read(p)
|
n, b.err = b.rd.Read(p)
|
||||||
@ -157,6 +197,7 @@ func (b *Reader) Read(p []byte) (n int, err error) {
|
|||||||
b.r += n
|
b.r += n
|
||||||
b.lastByte = int(b.buf[b.r-1])
|
b.lastByte = int(b.buf[b.r-1])
|
||||||
b.lastRuneSize = -1
|
b.lastRuneSize = -1
|
||||||
|
b.putBuf()
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,6 +214,9 @@ func (b *Reader) ReadByte() (c byte, err error) {
|
|||||||
c = b.buf[b.r]
|
c = b.buf[b.r]
|
||||||
b.r++
|
b.r++
|
||||||
b.lastByte = int(c)
|
b.lastByte = int(c)
|
||||||
|
if b.err != nil { // avoid putBuf call in the common case
|
||||||
|
b.putBuf()
|
||||||
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +224,7 @@ func (b *Reader) ReadByte() (c byte, err error) {
|
|||||||
func (b *Reader) UnreadByte() error {
|
func (b *Reader) UnreadByte() error {
|
||||||
b.lastRuneSize = -1
|
b.lastRuneSize = -1
|
||||||
if b.r == b.w && b.lastByte >= 0 {
|
if b.r == b.w && b.lastByte >= 0 {
|
||||||
|
b.allocBuf()
|
||||||
b.w = 1
|
b.w = 1
|
||||||
b.r = 0
|
b.r = 0
|
||||||
b.buf[0] = byte(b.lastByte)
|
b.buf[0] = byte(b.lastByte)
|
||||||
@ -381,7 +426,9 @@ func (b *Reader) ReadBytes(delim byte) (line []byte, err error) {
|
|||||||
// For simple uses, a Scanner may be more convenient.
|
// For simple uses, a Scanner may be more convenient.
|
||||||
func (b *Reader) ReadString(delim byte) (line string, err error) {
|
func (b *Reader) ReadString(delim byte) (line string, err error) {
|
||||||
bytes, err := b.ReadBytes(delim)
|
bytes, err := b.ReadBytes(delim)
|
||||||
return string(bytes), err
|
line = string(bytes)
|
||||||
|
b.putBuf()
|
||||||
|
return line, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo implements io.WriterTo.
|
// WriteTo implements io.WriterTo.
|
||||||
@ -416,6 +463,7 @@ func (b *Reader) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
func (b *Reader) writeBuf(w io.Writer) (int64, error) {
|
func (b *Reader) writeBuf(w io.Writer) (int64, error) {
|
||||||
n, err := w.Write(b.buf[b.r:b.w])
|
n, err := w.Write(b.buf[b.r:b.w])
|
||||||
b.r += n
|
b.r += n
|
||||||
|
b.putBuf()
|
||||||
return int64(n), err
|
return int64(n), err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -444,6 +492,7 @@ func NewWriterSize(wr io.Writer, size int) *Writer {
|
|||||||
size = defaultBufSize
|
size = defaultBufSize
|
||||||
}
|
}
|
||||||
b = new(Writer)
|
b = new(Writer)
|
||||||
|
// TODO(bradfitz): make Writer buffers lazy too, like Reader's
|
||||||
b.buf = make([]byte, size)
|
b.buf = make([]byte, size)
|
||||||
b.wr = wr
|
b.wr = wr
|
||||||
return b
|
return b
|
||||||
|
@ -1083,3 +1083,18 @@ func BenchmarkWriterCopyNoReadFrom(b *testing.B) {
|
|||||||
io.Copy(dst, src)
|
io.Copy(dst, src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkReaderEmpty(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
str := strings.Repeat("x", 16<<10)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
br := NewReader(strings.NewReader(str))
|
||||||
|
n, err := io.Copy(ioutil.Discard, br)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if n != int64(len(str)) {
|
||||||
|
b.Fatal("wrong length")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user