Skip to content

perf: avoid unnecessary allocations in hot paths#2902

Merged
sl0thentr0py merged 1 commit intomasterfrom
perf/hot-path-allocation-avoidance
Mar 19, 2026
Merged

perf: avoid unnecessary allocations in hot paths#2902
sl0thentr0py merged 1 commit intomasterfrom
perf/hot-path-allocation-avoidance

Conversation

@HazAT
Copy link
Member

@HazAT HazAT commented Mar 18, 2026

✅ Low risk — pure micro-optimizations, no new state or caching

Part of #2901 (reduce memory allocations by ~53%)

Changes

Backtrace::Line.parse:

  • Use indexed regex captures (match[1]) instead of .to_a destructuring — avoids intermediate array allocation
  • Add end_with? guard before .sub! for .class extension — skips string allocation on non-JRuby (99.9% of cases)
  • Use match? instead of =~ in #in_app — avoids MatchData allocation
  • Add nil guard for file in #in_app

StacktraceInterface::Frame:

  • Remove stored @project_root/@strip_backtrace_load_path ivars (passed as args instead) — fewer entries in Interface#to_h
  • Use byteslice instead of [] for filename prefix stripping
  • Replace chomp(File::SEPARATOR) with end_with? check — avoids string allocation
  • Inline under_project_root? to eliminate method call overhead

StacktraceBuilder#build:

  • Replace select + reverse + map + compact chain with single reverse while loop — eliminates 3 intermediate array allocations

RequestInterface:

  • Use delete_prefix instead of regex sub for HTTP_ prefix removal
  • Replace key.upcase != key with key.match?(LOWERCASE_PATTERN) — avoids allocating upcased string for every Rack env entry
  • Cache Rack version check — avoids repeated Gem::Version comparisons

HazAT added a commit that referenced this pull request Mar 18, 2026
Reduce total allocated memory from 442k to 206k bytes (-53.5%) and
objects from 3305 to 1538 (-53.5%) per Rails exception capture.

All changes are internal optimizations with zero behavior changes.

Key optimizations:
- Cache longest_load_path and compute_filename results (class-level,
  invalidated on $LOAD_PATH changes)
- Cache backtrace line parsing and Line/Frame object creation (bounded
  at 2048 entries)
- Optimize LineCache with Hash#fetch, direct context setting, and
  per-(filename, lineno) caching
- Avoid unnecessary allocations: indexed regex captures, match? instead
  of =~, byteslice, single-pass iteration in StacktraceBuilder
- RequestInterface: avoid env.dup, cache header name transforms, ASCII
  fast-path for encoding
- Scope/BreadcrumbBuffer: shallow dup instead of deep_dup where inner
  values are not mutated after duplication
- Hub#add_breadcrumb: hint default nil instead of {} to avoid empty
  hash allocation

See sub-PRs for detailed review by risk level:
- #2902 (low risk) — hot path allocation avoidance
- #2903 (low risk) — LineCache optimization
- #2904 (medium risk) — load path and filename caching
- #2905 (needs review) — backtrace parse caching
- #2906 (needs review) — Frame object caching
- #2907 (needs review) — Scope/BreadcrumbBuffer shallow dup
- #2908 (medium risk) — RequestInterface optimizations
@sl0thentr0py sl0thentr0py force-pushed the perf/hot-path-allocation-avoidance branch 3 times, most recently from 8932f03 to 10c81fd Compare March 19, 2026 15:59
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

@sl0thentr0py sl0thentr0py force-pushed the perf/hot-path-allocation-avoidance branch 3 times, most recently from 9c584bc to 332d863 Compare March 19, 2026 16:11
Reduce memory allocations during exception capture with zero-risk changes
that preserve identical behavior:

Backtrace::Line.parse:
- Use indexed regex captures (match[1]) instead of .to_a destructuring,
  avoiding intermediate array allocation
- Add end_with? guard before .sub! for .class extension — skips string
  allocation on non-JRuby (99.9% of cases)
- Use match? instead of =~ in #in_app to avoid MatchData allocation
- Add nil guard for file in #in_app

StacktraceInterface::Frame:
- Remove stored @project_root/@strip_backtrace_load_path ivars (passed as
  args to compute_filename instead) — fewer entries in Interface#to_h
- Use byteslice instead of [] for filename prefix stripping
- Replace chomp(File::SEPARATOR) with end_with? check to avoid allocation
- Inline under_project_root? to eliminate method call overhead

StacktraceBuilder#build:
- Replace select + reverse + map + compact chain with single reverse while
  loop, eliminating 3 intermediate array allocations

RequestInterface:
- Use delete_prefix instead of regex sub for HTTP_ prefix removal
- Replace key.upcase != key with key.match?(LOWERCASE_PATTERN) to avoid
  allocating an upcased string for every Rack env entry
- Cache Rack version check to avoid repeated Gem::Version comparisons
@sl0thentr0py sl0thentr0py force-pushed the perf/hot-path-allocation-avoidance branch from 332d863 to 0995848 Compare March 19, 2026 16:16
@sl0thentr0py sl0thentr0py merged commit 949bc6b into master Mar 19, 2026
135 checks passed
@sl0thentr0py sl0thentr0py deleted the perf/hot-path-allocation-avoidance branch March 19, 2026 16:24
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