From b5bfb5a3ce7c6d993dab40d9b92c06b873ecd404 Mon Sep 17 00:00:00 2001 From: Nick Ripley Date: Sun, 19 Nov 2023 20:01:57 -0500 Subject: [PATCH] runtime: refactor fpunwindExpand to use provided buffer fpunwindExpand currently allocates a new slice to hold the expanded call stack. In each place it's used, the resulting slice won't be needed immediately afterward, so the allocation is wasteful. Refactor fpunwindExpand to instead expand the call stack into a provided buffer. Change-Id: I05b26c191a8f76404c21ccbe3bd422325540425b Reviewed-on: https://go-review.googlesource.com/c/go/+/543715 Reviewed-by: Cherry Mui Auto-Submit: Cherry Mui LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Knyszek Reviewed-by: Michael Pratt --- src/runtime/tracestack.go | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/runtime/tracestack.go b/src/runtime/tracestack.go index 04b935a2c9..477526d7cb 100644 --- a/src/runtime/tracestack.go +++ b/src/runtime/tracestack.go @@ -147,20 +147,22 @@ func (t *traceStackTable) put(pcs []uintptr) uint64 { // releases all memory and resets state. It must only be called once the caller // can guarantee that there are no more writers to the table. func (t *traceStackTable) dump(gen uintptr) { + stackBuf := make([]uintptr, traceStackSize) w := unsafeTraceWriter(gen, nil) if root := (*traceMapNode)(t.tab.root.Load()); root != nil { - w = dumpStacksRec(root, w) + w = dumpStacksRec(root, w, stackBuf) } w.flush().end() t.tab.reset() } -func dumpStacksRec(node *traceMapNode, w traceWriter) traceWriter { +func dumpStacksRec(node *traceMapNode, w traceWriter, stackBuf []uintptr) traceWriter { stack := unsafe.Slice((*uintptr)(unsafe.Pointer(&node.data[0])), uintptr(len(node.data))/unsafe.Sizeof(uintptr(0))) // N.B. This might allocate, but that's OK because we're not writing to the M's buffer, // but one we're about to create (with ensure). - frames := makeTraceFrames(w.gen, fpunwindExpand(stack)) + n := fpunwindExpand(stackBuf, stack) + frames := makeTraceFrames(w.gen, stackBuf[:n]) // The maximum number of bytes required to hold the encoded stack, given that // it contains N frames. @@ -194,7 +196,7 @@ func dumpStacksRec(node *traceMapNode, w traceWriter) traceWriter { if child == nil { continue } - w = dumpStacksRec((*traceMapNode)(child), w) + w = dumpStacksRec((*traceMapNode)(child), w, stackBuf) } return w } @@ -260,31 +262,36 @@ func fpTracebackPCs(fp unsafe.Pointer, pcBuf []uintptr) (i int) { return i } +// fpunwindExpand expands a call stack from pcBuf into dst, +// returning the number of PCs written to dst. +// pcBuf and dst should not overlap. +// // fpunwindExpand checks if pcBuf contains logical frames (which include inlined // frames) or physical frames (produced by frame pointer unwinding) using a // sentinel value in pcBuf[0]. Logical frames are simply returned without the // sentinel. Physical frames are turned into logical frames via inline unwinding // and by applying the skip value that's stored in pcBuf[0]. -func fpunwindExpand(pcBuf []uintptr) []uintptr { +func fpunwindExpand(dst, pcBuf []uintptr) int { if len(pcBuf) > 0 && pcBuf[0] == logicalStackSentinel { // pcBuf contains logical rather than inlined frames, skip has already been // applied, just return it without the sentinel value in pcBuf[0]. - return pcBuf[1:] + return copy(dst, pcBuf[1:]) } var ( + n int lastFuncID = abi.FuncIDNormal - newPCBuf = make([]uintptr, 0, traceStackSize) skip = pcBuf[0] // skipOrAdd skips or appends retPC to newPCBuf and returns true if more // pcs can be added. skipOrAdd = func(retPC uintptr) bool { if skip > 0 { skip-- - } else { - newPCBuf = append(newPCBuf, retPC) + } else if n < len(dst) { + dst[n] = retPC + n++ } - return len(newPCBuf) < cap(newPCBuf) + return n < len(dst) } ) @@ -312,7 +319,7 @@ outer: lastFuncID = sf.funcID } } - return newPCBuf + return n } // startPCForTrace returns the start PC of a goroutine for tracing purposes. -- 2.48.1