Skip to content

perf(stacktrace): defer shortFuncName to kept frames; drop Sprintf in frame.String#136

Merged
samber merged 1 commit into
mainfrom
perf/stacktrace-frame-string
Jul 5, 2026
Merged

perf(stacktrace): defer shortFuncName to kept frames; drop Sprintf in frame.String#136
samber merged 1 commit into
mainfrom
perf/stacktrace-frame-string

Conversation

@samber

@samber samber commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • newStacktrace: compute shortFuncName only for frames that survive filtering — runtime/oops-internal frames (typically half the captured buffer) no longer pay the string slicing + Replacer allocation.
  • frame.String: direct string concatenation with strconv.Itoa instead of two reflection-based fmt.Sprintf calls — runs once per frame on every stack trace formatting.
  • getSourceFromFrame: builtin max/min instead of lo.Max/lo.Min, which allocated a []int per call.

Benchmarks

goos: darwin / goarch: arm64 / cpu: Apple M3, -count=10 -cpu=1

        │ before.txt  │             after.txt              │
        │   sec/op    │   sec/op     vs base               │
New       2.339µ ± 2%   2.202µ ± 4%  -5.84% (p=0.000 n=10)
Wrap      2.277µ ± 3%   2.122µ ± 3%  -6.79% (p=0.000 n=10)
geomean   2.307µ        2.162µ       -6.31%

        │  before.txt  │              after.txt              │
        │     B/op     │     B/op      vs base               │
New       2.352Ki ± 0%   2.336Ki ± 0%  -0.66% (p=0.000 n=10)
Wrap      2.336Ki ± 0%   2.320Ki ± 0%  -0.67% (p=0.000 n=10)
geomean   2.344Ki        2.328Ki       -0.67%

        │ before.txt │             after.txt              │
        │ allocs/op  │ allocs/op   vs base                │
New       15.00 ± 0%   13.00 ± 0%  -13.33% (p=0.000 n=10)
Wrap      14.00 ± 0%   12.00 ± 0%  -14.29% (p=0.000 n=10)
geomean   14.49        12.49       -13.81%

Test plan

  • go build ./...
  • go vet ./...
  • go test ./...

… frame.String

- newStacktrace: compute shortFuncName only for frames that survive
  filtering. Runtime/oops-internal frames (typically half the captured
  buffer) no longer pay the string slicing + Replacer allocation.
- frame.String: direct concatenation with strconv.Itoa instead of two
  reflection-based fmt.Sprintf calls; runs once per frame on every
  stack trace formatting.
- getSourceFromFrame: builtin max/min instead of lo.Max/lo.Min, which
  allocated a []int per call.

benchstat (darwin/arm64, Apple M3, -count=10 -cpu=1):

        │ before.txt  │             after.txt              │
        │   sec/op    │   sec/op     vs base               │
New       2.339µ ± 2%   2.202µ ± 4%  -5.84% (p=0.000 n=10)
Wrap      2.277µ ± 3%   2.122µ ± 3%  -6.79% (p=0.000 n=10)
geomean   2.307µ        2.162µ       -6.31%

        │  before.txt  │              after.txt              │
        │     B/op     │     B/op      vs base               │
New       2.352Ki ± 0%   2.336Ki ± 0%  -0.66% (p=0.000 n=10)
Wrap      2.336Ki ± 0%   2.320Ki ± 0%  -0.67% (p=0.000 n=10)
geomean   2.344Ki        2.328Ki       -0.67%

        │ before.txt │             after.txt              │
        │ allocs/op  │ allocs/op   vs base                │
New       15.00 ± 0%   13.00 ± 0%  -13.33% (p=0.000 n=10)
Wrap      14.00 ± 0%   12.00 ± 0%  -14.29% (p=0.000 n=10)
geomean   14.49        12.49       -13.81%
@codecov

codecov Bot commented Jul 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.17%. Comparing base (ddcaa21) to head (052c234).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #136      +/-   ##
==========================================
- Coverage   90.18%   90.17%   -0.02%     
==========================================
  Files          15       15              
  Lines        1233     1231       -2     
==========================================
- Hits         1112     1110       -2     
  Misses         96       96              
  Partials       25       25              
Flag Coverage Δ
unittests 90.17% <100.00%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@samber samber marked this pull request as ready for review July 5, 2026 14:34
Copilot AI review requested due to automatic review settings July 5, 2026 14:34
@samber samber merged commit b313311 into main Jul 5, 2026
16 of 17 checks passed
@samber samber deleted the perf/stacktrace-frame-string branch July 5, 2026 14:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes stacktrace capture and formatting in the oops package by reducing per-frame work (especially for filtered frames) and removing some allocation-heavy helpers in hot paths.

Changes:

  • Defer shortFuncName computation until after frame filtering in newStacktrace.
  • Replace fmt.Sprintf usage in oopsStacktraceFrame.String() with direct concatenation using strconv.Itoa.
  • Replace lo.Max/lo.Min in source extraction with Go 1.21 builtin max/min.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
stacktrace.go Reduces per-frame overhead in stack capture and speeds up frame string formatting.
sources.go Eliminates lo usage for min/max bounds in source context extraction to avoid per-call allocations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants