util/multierr: optimize New for nil cases (#6750)

Consider the following pattern:

	err1 := foo()
	err2 := bar()
	err3 := baz()
	return multierr.New(err1, err2, err3)

If err1, err2, and err3 are all nil, then multierr.New should not allocate.
Thus, modify the logic of New to count the number of distinct error values
and allocate the exactly needed slice. This also speeds up non-empty error
situation since repeatedly growing with append is slow.

Performance:

	name         old time/op    new time/op    delta
	Empty-24       41.8ns ± 2%     6.4ns ± 1%   -84.73%  (p=0.000 n=10+10)
	NonEmpty-24     120ns ± 3%      69ns ± 1%   -42.01%  (p=0.000 n=9+10)

	name         old alloc/op   new alloc/op   delta
	Empty-24        64.0B ± 0%      0.0B       -100.00%  (p=0.000 n=10+10)
	NonEmpty-24      168B ± 0%       88B ± 0%   -47.62%  (p=0.000 n=10+10)

	name         old allocs/op  new allocs/op  delta
	Empty-24         1.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)
	NonEmpty-24      3.00 ± 0%      2.00 ± 0%   -33.33%  (p=0.000 n=10+10)

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
This commit is contained in:
Joe Tsai
2022-12-14 18:17:38 -08:00
committed by GitHub
parent 55b24009f7
commit 350aab05e5
2 changed files with 43 additions and 13 deletions

View File

@@ -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)
}
}