]> Cypherpunks repositories - gostls13.git/commitdiff
io: make chained multiReader Read more efficient
authorandrew werner <andrew@upthere.com>
Tue, 15 Dec 2015 22:42:28 +0000 (14:42 -0800)
committerBrad Fitzpatrick <bradfitz@golang.org>
Mon, 16 May 2016 22:55:42 +0000 (22:55 +0000)
before this change, when io.MultiReader was called many times but contain few
underlying readers, calls to Read were unnecessarily expensive.

Fixes #13558

Change-Id: I3ec4e88c7b50c075b148331fb1b7348a5840adbe
Reviewed-on: https://go-review.googlesource.com/17873
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/io/multi.go
src/io/multi_test.go

index c23c12b151e132c397de655d4117133c61098bb9..ed05cac9e722d5fcff7663ffe55d188fe83c4283 100644 (file)
@@ -10,6 +10,13 @@ type multiReader struct {
 
 func (mr *multiReader) Read(p []byte) (n int, err error) {
        for len(mr.readers) > 0 {
+               // Optimization to flatten nested multiReaders (Issue 13558)
+               if len(mr.readers) == 1 {
+                       if r, ok := mr.readers[0].(*multiReader); ok {
+                               mr.readers = r.readers
+                               continue
+                       }
+               }
                n, err = mr.readers[0].Read(p)
                if n > 0 || err != EOF {
                        if err == EOF {
index 787ea341307a1122ee1178d6013f50200f822759..2dce36955ed2603e872bde2003981043303110d1 100644 (file)
@@ -7,9 +7,11 @@ package io_test
 import (
        "bytes"
        "crypto/sha1"
+       "errors"
        "fmt"
        . "io"
        "io/ioutil"
+       "runtime"
        "strings"
        "testing"
 )
@@ -164,3 +166,33 @@ func TestMultiWriterCopy(t *testing.T) {
                t.Errorf("buf.String() = %q, want %q", buf.String(), "hello world")
        }
 }
+
+// readerFunc is an io.Reader implemented by the underlying func.
+type readerFunc func(p []byte) (int, error)
+
+func (f readerFunc) Read(p []byte) (int, error) {
+       return f(p)
+}
+
+// Test that MultiReader properly flattens chained multiReaders when Read is called
+func TestMultiReaderFlatten(t *testing.T) {
+       pc := make([]uintptr, 1000) // 1000 should fit the full stack
+       var myDepth = runtime.Callers(0, pc)
+       var readDepth int // will contain the depth from which fakeReader.Read was called
+       var r Reader = MultiReader(readerFunc(func(p []byte) (int, error) {
+               readDepth = runtime.Callers(1, pc)
+               return 0, errors.New("irrelevant")
+       }))
+
+       // chain a bunch of multiReaders
+       for i := 0; i < 100; i++ {
+               r = MultiReader(r)
+       }
+
+       r.Read(nil) // don't care about errors, just want to check the call-depth for Read
+
+       if readDepth != myDepth+2 { // 2 should be multiReader.Read and fakeReader.Read
+               t.Errorf("multiReader did not flatten chained multiReaders: expected readDepth %d, got %d",
+                       myDepth+2, readDepth)
+       }
+}