Commit 99f67228 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

bufio: reuse Writer buffers after Flush

A bufio.Writer.Flush marks the usual end of a Writer's
life. Recycle its internal buffer on those explicit flushes,
but not on normal, as-needed internal flushes.

benchmark               old ns/op    new ns/op    delta
BenchmarkWriterEmpty         1959          727  -62.89%

benchmark              old allocs   new allocs    delta
BenchmarkWriterEmpty            2            1  -50.00%

benchmark               old bytes    new bytes    delta
BenchmarkWriterEmpty         4215           83  -98.03%

R=gri, iant
CC=gobot, golang-dev, voidlogic7
https://golang.org/cl/9459044
parent ddda7980
...@@ -29,7 +29,7 @@ var ( ...@@ -29,7 +29,7 @@ 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 // either nil or []byte of size bufSize buf []byte // either nil or []byte of length bufSize
bufSize int bufSize int
rd io.Reader rd io.Reader
r, w int r, w int
...@@ -314,7 +314,7 @@ func (b *Reader) ReadSlice(delim byte) (line []byte, err error) { ...@@ -314,7 +314,7 @@ func (b *Reader) ReadSlice(delim byte) (line []byte, err error) {
} }
// Buffer is full? // Buffer is full?
if b.Buffered() >= len(b.buf) { if b.Buffered() >= b.bufSize {
b.r = b.w b.r = b.w
return b.buf, ErrBufferFull return b.buf, ErrBufferFull
} }
...@@ -474,7 +474,8 @@ func (b *Reader) writeBuf(w io.Writer) (int64, error) { ...@@ -474,7 +474,8 @@ func (b *Reader) writeBuf(w io.Writer) (int64, error) {
// accepted and all subsequent writes will return the error. // accepted and all subsequent writes will return the error.
type Writer struct { type Writer struct {
err error err error
buf []byte buf []byte // either nil or []byte of length bufSize
bufSize int
n int n int
wr io.Writer wr io.Writer
} }
...@@ -485,16 +486,20 @@ type Writer struct { ...@@ -485,16 +486,20 @@ type Writer struct {
func NewWriterSize(wr io.Writer, size int) *Writer { func NewWriterSize(wr io.Writer, size int) *Writer {
// Is it already a Writer? // Is it already a Writer?
b, ok := wr.(*Writer) b, ok := wr.(*Writer)
if ok && len(b.buf) >= size { if ok && b.bufSize >= size {
return b return b
} }
if size <= 0 { if size <= 0 {
size = defaultBufSize size = defaultBufSize
} }
b = new(Writer) b = &Writer{
// TODO(bradfitz): make Writer buffers lazy too, like Reader's wr: wr,
b.buf = make([]byte, size) bufSize: size,
b.wr = wr }
if size > defaultBufSize {
// TODO(bradfitz): make all buffer sizes recycle
b.buf = make([]byte, b.bufSize)
}
return b return b
} }
...@@ -503,8 +508,38 @@ func NewWriter(wr io.Writer) *Writer { ...@@ -503,8 +508,38 @@ func NewWriter(wr io.Writer) *Writer {
return NewWriterSize(wr, defaultBufSize) return NewWriterSize(wr, defaultBufSize)
} }
// allocBuf makes b.buf non-nil.
func (b *Writer) 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 *Writer) putBuf() {
if b.n == 0 && cap(b.buf) == defaultBufSize {
select {
case bufCache <- b.buf:
b.buf = nil
default:
}
}
}
// Flush writes any buffered data to the underlying io.Writer. // Flush writes any buffered data to the underlying io.Writer.
func (b *Writer) Flush() error { func (b *Writer) Flush() error {
err := b.flush()
b.putBuf()
return err
}
func (b *Writer) flush() error {
if b.err != nil { if b.err != nil {
return b.err return b.err
} }
...@@ -528,7 +563,7 @@ func (b *Writer) Flush() error { ...@@ -528,7 +563,7 @@ func (b *Writer) Flush() error {
} }
// Available returns how many bytes are unused in the buffer. // Available returns how many bytes are unused in the buffer.
func (b *Writer) Available() int { return len(b.buf) - b.n } func (b *Writer) Available() int { return b.bufSize - b.n }
// Buffered returns the number of bytes that have been written into the current buffer. // Buffered returns the number of bytes that have been written into the current buffer.
func (b *Writer) Buffered() int { return b.n } func (b *Writer) Buffered() int { return b.n }
...@@ -538,6 +573,7 @@ func (b *Writer) Buffered() int { return b.n } ...@@ -538,6 +573,7 @@ func (b *Writer) Buffered() int { return b.n }
// If nn < len(p), it also returns an error explaining // If nn < len(p), it also returns an error explaining
// why the write is short. // why the write is short.
func (b *Writer) Write(p []byte) (nn int, err error) { func (b *Writer) Write(p []byte) (nn int, err error) {
b.allocBuf()
for len(p) > b.Available() && b.err == nil { for len(p) > b.Available() && b.err == nil {
var n int var n int
if b.Buffered() == 0 { if b.Buffered() == 0 {
...@@ -547,7 +583,7 @@ func (b *Writer) Write(p []byte) (nn int, err error) { ...@@ -547,7 +583,7 @@ func (b *Writer) Write(p []byte) (nn int, err error) {
} else { } else {
n = copy(b.buf[b.n:], p) n = copy(b.buf[b.n:], p)
b.n += n b.n += n
b.Flush() b.flush()
} }
nn += n nn += n
p = p[n:] p = p[n:]
...@@ -566,9 +602,12 @@ func (b *Writer) WriteByte(c byte) error { ...@@ -566,9 +602,12 @@ func (b *Writer) WriteByte(c byte) error {
if b.err != nil { if b.err != nil {
return b.err return b.err
} }
if b.Available() <= 0 && b.Flush() != nil { if b.Available() <= 0 && b.flush() != nil {
return b.err return b.err
} }
if b.buf == nil {
b.allocBuf()
}
b.buf[b.n] = c b.buf[b.n] = c
b.n++ b.n++
return nil return nil
...@@ -577,6 +616,9 @@ func (b *Writer) WriteByte(c byte) error { ...@@ -577,6 +616,9 @@ func (b *Writer) WriteByte(c byte) error {
// WriteRune writes a single Unicode code point, returning // WriteRune writes a single Unicode code point, returning
// the number of bytes written and any error. // the number of bytes written and any error.
func (b *Writer) WriteRune(r rune) (size int, err error) { func (b *Writer) WriteRune(r rune) (size int, err error) {
if b.buf == nil {
b.allocBuf()
}
if r < utf8.RuneSelf { if r < utf8.RuneSelf {
err = b.WriteByte(byte(r)) err = b.WriteByte(byte(r))
if err != nil { if err != nil {
...@@ -589,7 +631,7 @@ func (b *Writer) WriteRune(r rune) (size int, err error) { ...@@ -589,7 +631,7 @@ func (b *Writer) WriteRune(r rune) (size int, err error) {
} }
n := b.Available() n := b.Available()
if n < utf8.UTFMax { if n < utf8.UTFMax {
if b.Flush(); b.err != nil { if b.flush(); b.err != nil {
return 0, b.err return 0, b.err
} }
n = b.Available() n = b.Available()
...@@ -608,13 +650,14 @@ func (b *Writer) WriteRune(r rune) (size int, err error) { ...@@ -608,13 +650,14 @@ func (b *Writer) WriteRune(r rune) (size int, err error) {
// If the count is less than len(s), it also returns an error explaining // If the count is less than len(s), it also returns an error explaining
// why the write is short. // why the write is short.
func (b *Writer) WriteString(s string) (int, error) { func (b *Writer) WriteString(s string) (int, error) {
b.allocBuf()
nn := 0 nn := 0
for len(s) > b.Available() && b.err == nil { for len(s) > b.Available() && b.err == nil {
n := copy(b.buf[b.n:], s) n := copy(b.buf[b.n:], s)
b.n += n b.n += n
nn += n nn += n
s = s[n:] s = s[n:]
b.Flush() b.flush()
} }
if b.err != nil { if b.err != nil {
return nn, b.err return nn, b.err
...@@ -627,6 +670,7 @@ func (b *Writer) WriteString(s string) (int, error) { ...@@ -627,6 +670,7 @@ func (b *Writer) WriteString(s string) (int, error) {
// ReadFrom implements io.ReaderFrom. // ReadFrom implements io.ReaderFrom.
func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) { func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {
b.allocBuf()
if b.Buffered() == 0 { if b.Buffered() == 0 {
if w, ok := b.wr.(io.ReaderFrom); ok { if w, ok := b.wr.(io.ReaderFrom); ok {
return w.ReadFrom(r) return w.ReadFrom(r)
...@@ -641,7 +685,7 @@ func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) { ...@@ -641,7 +685,7 @@ func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {
b.n += m b.n += m
n += int64(m) n += int64(m)
if b.Available() == 0 { if b.Available() == 0 {
if err1 := b.Flush(); err1 != nil { if err1 := b.flush(); err1 != nil {
return n, err1 return n, err1
} }
} }
......
...@@ -1098,3 +1098,31 @@ func BenchmarkReaderEmpty(b *testing.B) { ...@@ -1098,3 +1098,31 @@ func BenchmarkReaderEmpty(b *testing.B) {
} }
} }
} }
func BenchmarkWriterEmpty(b *testing.B) {
b.ReportAllocs()
str := strings.Repeat("x", 1<<10)
bs := []byte(str)
for i := 0; i < b.N; i++ {
bw := NewWriter(ioutil.Discard)
bw.Flush()
bw.WriteByte('a')
bw.Flush()
bw.WriteRune('B')
bw.Flush()
bw.Write(bs)
bw.Flush()
bw.WriteString(str)
bw.Flush()
}
}
func BenchmarkWriterFlush(b *testing.B) {
b.ReportAllocs()
bw := NewWriter(ioutil.Discard)
str := strings.Repeat("x", 50)
for i := 0; i < b.N; i++ {
bw.WriteString(str)
bw.Flush()
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment