]> Cypherpunks repositories - gostls13.git/commitdiff
bufio: reuse Writer buffers after Flush
authorBrad Fitzpatrick <bradfitz@golang.org>
Tue, 21 May 2013 22:51:49 +0000 (15:51 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Tue, 21 May 2013 22:51:49 +0000 (15:51 -0700)
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

src/pkg/bufio/bufio.go
src/pkg/bufio/bufio_test.go

index cc3fe4d3cd40374a9d8f8508adc54e7e2b6f003d..4df5e32d9ae0a79f6eeec79481785475a9f45de0 100644 (file)
@@ -29,7 +29,7 @@ var (
 
 // Reader implements buffering for an io.Reader object.
 type Reader struct {
-       buf          []byte // either nil or []byte of size bufSize
+       buf          []byte // either nil or []byte of length bufSize
        bufSize      int
        rd           io.Reader
        r, w         int
@@ -314,7 +314,7 @@ func (b *Reader) ReadSlice(delim byte) (line []byte, err error) {
                }
 
                // Buffer is full?
-               if b.Buffered() >= len(b.buf) {
+               if b.Buffered() >= b.bufSize {
                        b.r = b.w
                        return b.buf, ErrBufferFull
                }
@@ -473,10 +473,11 @@ func (b *Reader) writeBuf(w io.Writer) (int64, error) {
 // If an error occurs writing to a Writer, no more data will be
 // accepted and all subsequent writes will return the error.
 type Writer struct {
-       err error
-       buf []byte
-       n   int
-       wr  io.Writer
+       err     error
+       buf     []byte // either nil or []byte of length bufSize
+       bufSize int
+       n       int
+       wr      io.Writer
 }
 
 // NewWriterSize returns a new Writer whose buffer has at least the specified
@@ -485,16 +486,20 @@ type Writer struct {
 func NewWriterSize(wr io.Writer, size int) *Writer {
        // Is it already a Writer?
        b, ok := wr.(*Writer)
-       if ok && len(b.buf) >= size {
+       if ok && b.bufSize >= size {
                return b
        }
        if size <= 0 {
                size = defaultBufSize
        }
-       b = new(Writer)
-       // TODO(bradfitz): make Writer buffers lazy too, like Reader's
-       b.buf = make([]byte, size)
-       b.wr = wr
+       b = &Writer{
+               wr:      wr,
+               bufSize: size,
+       }
+       if size > defaultBufSize {
+               // TODO(bradfitz): make all buffer sizes recycle
+               b.buf = make([]byte, b.bufSize)
+       }
        return b
 }
 
@@ -503,8 +508,38 @@ func NewWriter(wr io.Writer) *Writer {
        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.
 func (b *Writer) Flush() error {
+       err := b.flush()
+       b.putBuf()
+       return err
+}
+
+func (b *Writer) flush() error {
        if b.err != nil {
                return b.err
        }
@@ -528,7 +563,7 @@ func (b *Writer) Flush() error {
 }
 
 // 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.
 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
 // why the write is short.
 func (b *Writer) Write(p []byte) (nn int, err error) {
+       b.allocBuf()
        for len(p) > b.Available() && b.err == nil {
                var n int
                if b.Buffered() == 0 {
@@ -547,7 +583,7 @@ func (b *Writer) Write(p []byte) (nn int, err error) {
                } else {
                        n = copy(b.buf[b.n:], p)
                        b.n += n
-                       b.Flush()
+                       b.flush()
                }
                nn += n
                p = p[n:]
@@ -566,9 +602,12 @@ func (b *Writer) WriteByte(c byte) error {
        if b.err != nil {
                return b.err
        }
-       if b.Available() <= 0 && b.Flush() != nil {
+       if b.Available() <= 0 && b.flush() != nil {
                return b.err
        }
+       if b.buf == nil {
+               b.allocBuf()
+       }
        b.buf[b.n] = c
        b.n++
        return nil
@@ -577,6 +616,9 @@ func (b *Writer) WriteByte(c byte) error {
 // WriteRune writes a single Unicode code point, returning
 // the number of bytes written and any error.
 func (b *Writer) WriteRune(r rune) (size int, err error) {
+       if b.buf == nil {
+               b.allocBuf()
+       }
        if r < utf8.RuneSelf {
                err = b.WriteByte(byte(r))
                if err != nil {
@@ -589,7 +631,7 @@ func (b *Writer) WriteRune(r rune) (size int, err error) {
        }
        n := b.Available()
        if n < utf8.UTFMax {
-               if b.Flush(); b.err != nil {
+               if b.flush(); b.err != nil {
                        return 0, b.err
                }
                n = b.Available()
@@ -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
 // why the write is short.
 func (b *Writer) WriteString(s string) (int, error) {
+       b.allocBuf()
        nn := 0
        for len(s) > b.Available() && b.err == nil {
                n := copy(b.buf[b.n:], s)
                b.n += n
                nn += n
                s = s[n:]
-               b.Flush()
+               b.flush()
        }
        if b.err != nil {
                return nn, b.err
@@ -627,6 +670,7 @@ func (b *Writer) WriteString(s string) (int, error) {
 
 // ReadFrom implements io.ReaderFrom.
 func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {
+       b.allocBuf()
        if b.Buffered() == 0 {
                if w, ok := b.wr.(io.ReaderFrom); ok {
                        return w.ReadFrom(r)
@@ -641,7 +685,7 @@ func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {
                b.n += m
                n += int64(m)
                if b.Available() == 0 {
-                       if err1 := b.Flush(); err1 != nil {
+                       if err1 := b.flush(); err1 != nil {
                                return n, err1
                        }
                }
index bc5e53abce19914264f625266bfb038a9ae13381..93f1b3fe086fabbdff4573cccc65851eb9e1a3fd 100644 (file)
@@ -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()
+       }
+}