diff --git a/util/multierr/multierr.go b/util/multierr/multierr.go index 83a5cb17f..b4ae0afe5 100644 --- a/util/multierr/multierr.go +++ b/util/multierr/multierr.go @@ -53,7 +53,31 @@ func (e Error) Unwrap() []error { // If the resulting slice has length 1, New returns that error. // If the resulting slice has length > 1, New returns that slice as an Error. func New(errs ...error) error { - dst := make([]error, 0, len(errs)) + // First count the number of errors to avoid allocating. + var n int + var errFirst error + for _, e := range errs { + switch e := e.(type) { + case nil: + continue + case Error: + n += len(e.errs) + if errFirst == nil && len(e.errs) > 0 { + errFirst = e.errs[0] + } + default: + n++ + if errFirst == nil { + errFirst = e + } + } + } + if n <= 1 { + return errFirst // nil if n == 0 + } + + // More than one error, allocate slice and construct the multi-error. + dst := make([]error, 0, n) for _, e := range errs { switch e := e.(type) { case nil: @@ -64,18 +88,6 @@ func New(errs ...error) error { dst = append(dst, e) } } - // dst has been filtered and splatted. - switch len(dst) { - case 0: - return nil - case 1: - return dst[0] - } - // Zero any remaining elements of dst left over from errs, for GC. - tail := dst[len(dst):cap(dst)] - for i := range tail { - tail[i] = nil - } return Error{errs: dst} } diff --git a/util/multierr/multierr_test.go b/util/multierr/multierr_test.go index f8cb729ba..28b4efdb9 100644 --- a/util/multierr/multierr_test.go +++ b/util/multierr/multierr_test.go @@ -7,6 +7,7 @@ package multierr_test import ( "errors" "fmt" + "io" "testing" qt "github.com/frankban/quicktest" @@ -106,3 +107,20 @@ func TestRange(t *testing.T) { return x.Error() == y.Error() })), want) } + +var sink error + +func BenchmarkEmpty(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + sink = multierr.New(nil, nil, nil, multierr.Error{}) + } +} + +func BenchmarkNonEmpty(b *testing.B) { + merr := multierr.New(io.ErrShortBuffer, io.ErrNoProgress) + b.ReportAllocs() + for i := 0; i < b.N; i++ { + sink = multierr.New(io.ErrUnexpectedEOF, merr, io.ErrClosedPipe) + } +}