From: andrew werner Date: Tue, 15 Dec 2015 22:42:28 +0000 (-0800) Subject: io: make chained multiReader Read more efficient X-Git-Tag: go1.7beta1~197 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=ccdca832c569727f7985966a3324421a69739f57;p=gostls13.git io: make chained multiReader Read more efficient 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 Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- diff --git a/src/io/multi.go b/src/io/multi.go index c23c12b151..ed05cac9e7 100644 --- a/src/io/multi.go +++ b/src/io/multi.go @@ -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 { diff --git a/src/io/multi_test.go b/src/io/multi_test.go index 787ea34130..2dce36955e 100644 --- a/src/io/multi_test.go +++ b/src/io/multi_test.go @@ -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) + } +}