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...)
+				}
+			})
+		})
+	}
+}