From 1626c0edddfe9d859f7d26544d6b32f73e39ad1a Mon Sep 17 00:00:00 2001 From: Chris Stockton Date: Mon, 28 Jan 2019 07:45:49 -0700 Subject: [PATCH] reduce allocations when printing stack traces Use a bytes.Buffer grown to len(stack)*stackMinLen for writing to format. ---- benchmark old ns/op new ns/op delta BenchmarkStackFormatting/%+v-stack-10-24 20692 16377 -20.85% BenchmarkStackFormatting/%+v-stack-30-24 43502 30200 -30.58% BenchmarkStackFormatting/%+v-stack-60-24 43166 29790 -30.99% benchmark old allocs new allocs delta BenchmarkStackFormatting/%+v-stack-10-24 19 6 -68.42% BenchmarkStackFormatting/%+v-stack-30-24 33 3 -90.91% BenchmarkStackFormatting/%+v-stack-60-24 33 3 -90.91% benchmark old bytes new bytes delta BenchmarkStackFormatting/%+v-stack-10-24 1427 2942 +106.17% BenchmarkStackFormatting/%+v-stack-30-24 3341 6261 +87.40% BenchmarkStackFormatting/%+v-stack-60-24 3341 6261 +87.40% --- stack.go | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/stack.go b/stack.go index 81f88564..f4a99d6b 100644 --- a/stack.go +++ b/stack.go @@ -1,6 +1,7 @@ package errors import ( + "bytes" "fmt" "io" "path" @@ -61,25 +62,27 @@ func (f Frame) name() string { // %+s function name and path of source file relative to the compile time // GOPATH separated by \n\t (\n\t) // %+v equivalent to %+s:%d -func (f Frame) Format(s fmt.State, verb rune) { +func (f Frame) Format(s fmt.State, verb rune) { f.format(s, s, verb) } + +func (f Frame) format(w io.Writer, s fmt.State, verb rune) { switch verb { case 's': switch { case s.Flag('+'): - io.WriteString(s, f.name()) - io.WriteString(s, "\n\t") - io.WriteString(s, f.file()) + io.WriteString(w, f.name()) + io.WriteString(w, "\n\t") + io.WriteString(w, f.file()) default: - io.WriteString(s, path.Base(f.file())) + io.WriteString(w, path.Base(f.file())) } case 'd': - io.WriteString(s, strconv.Itoa(f.line())) + io.WriteString(w, strconv.Itoa(f.line())) case 'n': - io.WriteString(s, funcname(f.name())) + io.WriteString(w, funcname(f.name())) case 'v': - f.Format(s, 's') - io.WriteString(s, ":") - f.Format(s, 'd') + f.format(w, s, 's') + io.WriteString(w, ":") + f.format(w, s, 'd') } } @@ -126,6 +129,11 @@ func (st StackTrace) formatSlice(s fmt.State, verb rune) { 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 @@ -134,10 +142,14 @@ func (s *stack) Format(st fmt.State, verb rune) { case 'v': switch { case st.Flag('+'): + var b bytes.Buffer + b.Grow(len(*s) * stackMinLen) for _, pc := range *s { f := Frame(pc) - fmt.Fprintf(st, "\n%+v", f) + b.WriteByte('\n') + f.format(&b, st, 'v') } + io.Copy(st, &b) } } }