Skip to content

Commit ffb6e22

Browse files
authored
Reduce allocations in StackTrace.Format (pkg#194)
Updates pkg#150 Signed-off-by: Dave Cheney <[email protected]>
1 parent 565c8d0 commit ffb6e22

File tree

2 files changed

+25
-46
lines changed

2 files changed

+25
-46
lines changed

stack.go

+24-41
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package errors
22

33
import (
4-
"bytes"
54
"fmt"
65
"io"
76
"path"
@@ -11,6 +10,8 @@ import (
1110
)
1211

1312
// Frame represents a program counter inside a stack frame.
13+
// For historical reasons if Frame is interpreted as a uintptr
14+
// its value represents the program counter + 1.
1415
type Frame uintptr
1516

1617
// pc returns the program counter for this frame;
@@ -61,29 +62,24 @@ func (f Frame) name() string {
6162
// GOPATH separated by \n\t (<funcname>\n\t<path>)
6263
// %+v equivalent to %+s:%d
6364
func (f Frame) Format(s fmt.State, verb rune) {
64-
f.format(s, s, verb)
65-
}
66-
67-
// format allows stack trace printing calls to be made with a bytes.Buffer.
68-
func (f Frame) format(w io.Writer, s fmt.State, verb rune) {
6965
switch verb {
7066
case 's':
7167
switch {
7268
case s.Flag('+'):
73-
io.WriteString(w, f.name())
74-
io.WriteString(w, "\n\t")
75-
io.WriteString(w, f.file())
69+
io.WriteString(s, f.name())
70+
io.WriteString(s, "\n\t")
71+
io.WriteString(s, f.file())
7672
default:
77-
io.WriteString(w, path.Base(f.file()))
73+
io.WriteString(s, path.Base(f.file()))
7874
}
7975
case 'd':
80-
io.WriteString(w, strconv.Itoa(f.line()))
76+
io.WriteString(s, strconv.Itoa(f.line()))
8177
case 'n':
82-
io.WriteString(w, funcname(f.name()))
78+
io.WriteString(s, funcname(f.name()))
8379
case 'v':
84-
f.format(w, s, 's')
85-
io.WriteString(w, ":")
86-
f.format(w, s, 'd')
80+
f.Format(s, 's')
81+
io.WriteString(s, ":")
82+
f.Format(s, 'd')
8783
}
8884
}
8985

@@ -99,50 +95,37 @@ type StackTrace []Frame
9995
//
10096
// %+v Prints filename, function, and line number for each Frame in the stack.
10197
func (st StackTrace) Format(s fmt.State, verb rune) {
102-
var b bytes.Buffer
10398
switch verb {
10499
case 'v':
105100
switch {
106101
case s.Flag('+'):
107-
b.Grow(len(st) * stackMinLen)
108102
for _, f := range st {
109-
b.WriteByte('\n')
110-
f.format(&b, s, verb)
103+
io.WriteString(s, "\n")
104+
f.Format(s, verb)
111105
}
112106
case s.Flag('#'):
113-
fmt.Fprintf(&b, "%#v", []Frame(st))
107+
fmt.Fprintf(s, "%#v", []Frame(st))
114108
default:
115-
st.formatSlice(&b, s, verb)
109+
st.formatSlice(s, verb)
116110
}
117111
case 's':
118-
st.formatSlice(&b, s, verb)
112+
st.formatSlice(s, verb)
119113
}
120-
io.Copy(s, &b)
121114
}
122115

123116
// formatSlice will format this StackTrace into the given buffer as a slice of
124117
// Frame, only valid when called with '%s' or '%v'.
125-
func (st StackTrace) formatSlice(b *bytes.Buffer, s fmt.State, verb rune) {
126-
b.WriteByte('[')
127-
if len(st) == 0 {
128-
b.WriteByte(']')
129-
return
130-
}
131-
132-
b.Grow(len(st) * (stackMinLen / 4))
133-
st[0].format(b, s, verb)
134-
for _, fr := range st[1:] {
135-
b.WriteByte(' ')
136-
fr.format(b, s, verb)
118+
func (st StackTrace) formatSlice(s fmt.State, verb rune) {
119+
io.WriteString(s, "[")
120+
for i, f := range st {
121+
if i > 0 {
122+
io.WriteString(s, " ")
123+
}
124+
f.Format(s, verb)
137125
}
138-
b.WriteByte(']')
126+
io.WriteString(s, "]")
139127
}
140128

141-
// stackMinLen is a best-guess at the minimum length of a stack trace. It
142-
// doesn't need to be exact, just give a good enough head start for the buffer
143-
// to avoid the expensive early growth.
144-
const stackMinLen = 96
145-
146129
// stack represents a stack of program counters.
147130
type stack []uintptr
148131

stack_test.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func TestStackTrace(t *testing.T) {
133133
"\t.+/github.com/pkg/errors/stack_test.go:131", // this is the stack of New
134134
},
135135
}, {
136-
func() error { noinline(); return New("ooh") }(), []string{
136+
func() error { return New("ooh") }(), []string{
137137
`github.com/pkg/errors.TestStackTrace.func1` +
138138
"\n\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New
139139
"github.com/pkg/errors.TestStackTrace\n" +
@@ -248,7 +248,3 @@ func caller() Frame {
248248
frame, _ := frames.Next()
249249
return Frame(frame.PC)
250250
}
251-
252-
//go:noinline
253-
// noinline prevents the caller being inlined
254-
func noinline() {}

0 commit comments

Comments
 (0)