Skip to content

perf: use shallow dup instead of deep_dup for Scope and BreadcrumbBuffer#2907

Closed
HazAT wants to merge 1 commit intomasterfrom
perf/scope-and-breadcrumb-shallow-dup
Closed

perf: use shallow dup instead of deep_dup for Scope and BreadcrumbBuffer#2907
HazAT wants to merge 1 commit intomasterfrom
perf/scope-and-breadcrumb-shallow-dup

Conversation

@HazAT
Copy link
Member

@HazAT HazAT commented Mar 18, 2026

⚠️ Needs closer review — changes copy semantics for scope containers

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

Changes

Replace deep_dup with dup for:

  • Scope: contexts, extra, tags, user, fingerprint
  • BreadcrumbBuffer: buffer array

Rationale

Scope: These containers are not mutated in-place after duplication. Scope methods like set_tags, set_extras, set_user all use assignment (replacing the entire hash) or merge! on the copy's own hash. The inner values (strings, numbers, symbols) are immutable or treated as such.

BreadcrumbBuffer: Individual Breadcrumb objects in the buffer are not mutated after being recorded — they are only read during serialization. A shallow dup of the buffer array is sufficient to prevent the copy from seeing new breadcrumbs added to the original.

span, session, and propagation_context still use deep_dup as they contain mutable nested state that may be modified in-place.

Review focus

  • Are there any code paths where inner hash values in contexts/extra/tags/user are mutated in-place (rather than replaced)?
  • Are breadcrumbs ever modified after being added to the buffer?
  • All existing tests pass with this change.

⚠️ Needs closer review — changes copy semantics for scope containers.

Replace deep_dup with dup for Scope's contexts, extra, tags, user, and
fingerprint hashes, and for BreadcrumbBuffer's buffer array.

Rationale: These containers are not mutated in-place after duplication.
Scope methods like set_tags, set_extras, set_user all use assignment
(replacing the entire hash) or merge! on the copy's own hash. The inner
values (strings, numbers, symbols) are immutable or treated as such.

BreadcrumbBuffer: Individual Breadcrumb objects in the buffer are not
mutated after being recorded — they are only read during serialization.
A shallow dup of the buffer array is sufficient to prevent the copy
from seeing new breadcrumbs added to the original.

span, session, and propagation_context still use deep_dup as they
contain mutable nested state that may be modified in-place.
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
Copy link
Member

sl0thentr0py commented Mar 19, 2026

don't want to risk this and the % change in allocation is not that high.
Once we move to attributes, we will keep this in mind and make the API cleaner so we don't need deep_dup.

### 13. BreadcrumbBuffer shallow dup — 2,552 bytes saved (-0.8%)

`.dup` instead of `.deep_dup` for the buffer array. Breadcrumbs are not mutated after being recorded.

**Files:** `sentry-ruby/lib/sentry/breadcrumb_buffer.rb`

---

### 14. Scope shallow dup — 2,528 bytes saved (-1.1%)

`.dup` instead of `.deep_dup` for `contexts`, `extra`, `tags`, `user`, `fingerprint`. Inner values are not mutated after scope duplication, only replaced via `merge!` or assignment.

**Files:** `sentry-ruby/lib/sentry/scope.rb`

---

@sl0thentr0py sl0thentr0py deleted the perf/scope-and-breadcrumb-shallow-dup branch March 19, 2026 15:46
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