errors/fmt: added benchmarks for stack trace
Removing the intermediate errorf improves
performance by 12-18%.
Old: with intermediate errorf, New: errorf removed:
name old time/op new time/op delta
Errorf/no_format/ExpWithTrace-8 626ns ± 6% 514ns ± 8% -17.92% (p=0.029 n=4+4)
Errorf/no_format/ExpNoTrace-8 116ns ± 1% 112ns ± 1% -3.03% (p=0.029 n=4+4)
Errorf/no_format/Core-8 226ns ± 1% 227ns ± 1% ~ (p=0.714 n=4+4)
Errorf/with_format/ExpWithTrace-8 668ns ± 1% 564ns ± 1% -15.59% (p=0.029 n=4+4)
Errorf/with_format/ExpNoTrace-8 170ns ± 1% 166ns ± 2% ~ (p=0.057 n=4+4)
Errorf/with_format/Core-8 274ns ± 1% 272ns ± 1% ~ (p=0.343 n=4+4)
Errorf/method:_mytype/ExpWithTrace-8 938ns ± 2% 820ns ± 1% -12.56% (p=0.029 n=4+4)
Errorf/method:_mytype/ExpNoTrace-8 404ns ± 2% 390ns ± 2% -3.47% (p=0.029 n=4+4)
Errorf/method:_mytype/Core-8 358ns ± 1% 358ns ± 1% ~ (p=0.657 n=4+4)
Errorf/method:_number/ExpWithTrace-8 1.03µs ± 1% 0.90µs ± 1% -12.70% (p=0.029 n=4+4)
Errorf/method:_number/ExpNoTrace-8 496ns ± 2% 480ns ± 0% -3.12% (p=0.029 n=4+4)
Errorf/method:_number/Core-8 652ns ± 2% 659ns ± 2% ~ (p=0.343 n=4+4)
Change-Id: Ied17d4265342507f7c9c39977f2fddd416c6e503
Reviewed-on: https://go-review.googlesource.com/c/146197
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/errors/fmt/errors.go b/errors/fmt/errors.go
index a94ba3e..a4de524 100644
--- a/errors/fmt/errors.go
+++ b/errors/fmt/errors.go
@@ -9,15 +9,18 @@
"strings"
"golang.org/x/exp/errors"
+ "golang.org/x/exp/errors/internal"
)
-// fmtError formats err according to verb, writing to p.
-// If it cannot handle the error, it does no formatting
-// and returns false.
-func errorf(format string, a []interface{}) error {
+// Errorf formats according to a format specifier and returns the string
+// as a value that satisfies error.
+//
+// The returned error includes the file and line number of the caller
+// when formatted with additional detail enabled.
+func Errorf(format string, a ...interface{}) error {
err := lastError(format, a)
if err == nil {
- return &simpleErr{Sprintf(format, a...), errors.Caller(2)}
+ return &simpleErr{Sprintf(format, a...), errors.Caller(1)}
}
// TODO: this is not entirely correct. The error value could be
@@ -26,10 +29,13 @@
// have it optionally ignore extra arguments and pass the argument
// list in its entirety.
format = format[:len(format)-len(": %s")]
+ if !internal.EnableTrace {
+ return &withChain{msg: Sprintf(format, a[:len(a)-1]...), err: err}
+ }
return &withChain{
msg: Sprintf(format, a[:len(a)-1]...),
err: err,
- frame: errors.Caller(2),
+ frame: errors.Caller(1),
}
}
diff --git a/errors/fmt/print.go b/errors/fmt/print.go
index e16670a..0374b0f 100644
--- a/errors/fmt/print.go
+++ b/errors/fmt/print.go
@@ -205,15 +205,6 @@
return s
}
-// Errorf formats according to a format specifier and returns the string
-// as a value that satisfies error.
-//
-// The returned error includes the file and line number of the caller
-// when formatted with additional detail enabled.
-func Errorf(format string, a ...interface{}) error {
- return errorf(format, a)
-}
-
// These routines do not take a format string
// Fprint formats using the default formats for its operands and writes to w.
diff --git a/errors/internal/internal.go b/errors/internal/internal.go
new file mode 100644
index 0000000..89f4eca
--- /dev/null
+++ b/errors/internal/internal.go
@@ -0,0 +1,8 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+// EnableTrace indicates whether stack information should be recorded in errors.
+var EnableTrace = true
diff --git a/errors/stack_test.go b/errors/stack_test.go
new file mode 100644
index 0000000..3d11769
--- /dev/null
+++ b/errors/stack_test.go
@@ -0,0 +1,61 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errors_test
+
+import (
+ "bytes"
+ fmtcore "fmt"
+ "math/big"
+ "testing"
+
+ "golang.org/x/exp/errors"
+ "golang.org/x/exp/errors/fmt"
+ "golang.org/x/exp/errors/internal"
+)
+
+type myType struct{}
+
+func (myType) Format(s fmt.State, v rune) {
+ s.Write(bytes.Repeat([]byte("Hi! "), 10))
+}
+
+func BenchmarkErrorf(b *testing.B) {
+ err := errors.New("foo")
+ // pi := big.NewFloat(3.14) // Something expensive.
+ num := big.NewInt(5)
+ args := func(a ...interface{}) []interface{} { return a }
+ benchCases := []struct {
+ name string
+ format string
+ args []interface{}
+ }{
+ {"no_format", "msg: %v", args(err)},
+ {"with_format", "failed %d times: %v", args(5, err)},
+ {"method: mytype", "pi: %v", args("myfile.go", myType{}, err)},
+ {"method: number", "pi: %v", args("myfile.go", num, err)},
+ }
+ for _, bc := range benchCases {
+ b.Run(bc.name, func(b *testing.B) {
+ b.Run("ExpWithTrace", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ fmt.Errorf(bc.format, bc.args...)
+ }
+ })
+ b.Run("ExpNoTrace", func(b *testing.B) {
+ internal.EnableTrace = false
+ defer func() { internal.EnableTrace = true }()
+
+ for i := 0; i < b.N; i++ {
+ fmt.Errorf(bc.format, bc.args...)
+ }
+ })
+ b.Run("Core", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ fmtcore.Errorf(bc.format, bc.args...)
+ }
+ })
+ })
+ }
+}