Reduce allocations in StackTrace.Format
Updates #150
Signed-off-by: Dave Cheney <dave@cheney.net>
diff --git a/stack.go b/stack.go
index ddc8c30..81f8856 100644
--- a/stack.go
+++ b/stack.go
@@ -1,7 +1,6 @@
package errors
import (
- "bytes"
"fmt"
"io"
"path"
@@ -11,6 +10,8 @@
)
// Frame represents a program counter inside a stack frame.
+// For historical reasons if Frame is interpreted as a uintptr
+// its value represents the program counter + 1.
type Frame uintptr
// pc returns the program counter for this frame;
@@ -61,29 +62,24 @@
// GOPATH separated by \n\t (<funcname>\n\t<path>)
// %+v equivalent to %+s:%d
func (f Frame) Format(s fmt.State, verb rune) {
- f.format(s, s, verb)
-}
-
-// format allows stack trace printing calls to be made with a bytes.Buffer.
-func (f Frame) format(w io.Writer, s fmt.State, verb rune) {
switch verb {
case 's':
switch {
case s.Flag('+'):
- io.WriteString(w, f.name())
- io.WriteString(w, "\n\t")
- io.WriteString(w, f.file())
+ io.WriteString(s, f.name())
+ io.WriteString(s, "\n\t")
+ io.WriteString(s, f.file())
default:
- io.WriteString(w, path.Base(f.file()))
+ io.WriteString(s, path.Base(f.file()))
}
case 'd':
- io.WriteString(w, strconv.Itoa(f.line()))
+ io.WriteString(s, strconv.Itoa(f.line()))
case 'n':
- io.WriteString(w, funcname(f.name()))
+ io.WriteString(s, funcname(f.name()))
case 'v':
- f.format(w, s, 's')
- io.WriteString(w, ":")
- f.format(w, s, 'd')
+ f.Format(s, 's')
+ io.WriteString(s, ":")
+ f.Format(s, 'd')
}
}
@@ -99,50 +95,37 @@
//
// %+v Prints filename, function, and line number for each Frame in the stack.
func (st StackTrace) Format(s fmt.State, verb rune) {
- var b bytes.Buffer
switch verb {
case 'v':
switch {
case s.Flag('+'):
- b.Grow(len(st) * stackMinLen)
for _, f := range st {
- b.WriteByte('\n')
- f.format(&b, s, verb)
+ io.WriteString(s, "\n")
+ f.Format(s, verb)
}
case s.Flag('#'):
- fmt.Fprintf(&b, "%#v", []Frame(st))
+ fmt.Fprintf(s, "%#v", []Frame(st))
default:
- st.formatSlice(&b, s, verb)
+ st.formatSlice(s, verb)
}
case 's':
- st.formatSlice(&b, s, verb)
+ st.formatSlice(s, verb)
}
- io.Copy(s, &b)
}
// formatSlice will format this StackTrace into the given buffer as a slice of
// Frame, only valid when called with '%s' or '%v'.
-func (st StackTrace) formatSlice(b *bytes.Buffer, s fmt.State, verb rune) {
- b.WriteByte('[')
- if len(st) == 0 {
- b.WriteByte(']')
- return
+func (st StackTrace) formatSlice(s fmt.State, verb rune) {
+ io.WriteString(s, "[")
+ for i, f := range st {
+ if i > 0 {
+ io.WriteString(s, " ")
+ }
+ f.Format(s, verb)
}
-
- b.Grow(len(st) * (stackMinLen / 4))
- st[0].format(b, s, verb)
- for _, fr := range st[1:] {
- b.WriteByte(' ')
- fr.format(b, s, verb)
- }
- b.WriteByte(']')
+ io.WriteString(s, "]")
}
-// stackMinLen is a best-guess at the minimum length of a stack trace. It
-// doesn't need to be exact, just give a good enough head start for the buffer
-// to avoid the expensive early growth.
-const stackMinLen = 96
-
// stack represents a stack of program counters.
type stack []uintptr
diff --git a/stack_test.go b/stack_test.go
index 172a57d..1acd719 100644
--- a/stack_test.go
+++ b/stack_test.go
@@ -133,7 +133,7 @@
"\t.+/github.com/pkg/errors/stack_test.go:131", // this is the stack of New
},
}, {
- func() error { noinline(); return New("ooh") }(), []string{
+ func() error { return New("ooh") }(), []string{
`github.com/pkg/errors.TestStackTrace.func1` +
"\n\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New
"github.com/pkg/errors.TestStackTrace\n" +
@@ -248,7 +248,3 @@
frame, _ := frames.Next()
return Frame(frame.PC)
}
-
-//go:noinline
-// noinline prevents the caller being inlined
-func noinline() {}