Skip to content

perf(sdk): store InstrumentationScope in Arc for cheaper tracer clones#3378

Open
bryantbiggs wants to merge 6 commits intoopen-telemetry:mainfrom
bryantbiggs:perf/avoid-scope-clone-per-processor
Open

perf(sdk): store InstrumentationScope in Arc for cheaper tracer clones#3378
bryantbiggs wants to merge 6 commits intoopen-telemetry:mainfrom
bryantbiggs:perf/avoid-scope-clone-per-processor

Conversation

@bryantbiggs
Copy link
Contributor

@bryantbiggs bryantbiggs commented Feb 20, 2026

Summary

SdkTracer now stores its InstrumentationScope in Arc. Since tracers are cloned on every span creation (build_recording_span calls self.clone()), this turns the scope copy from a deep clone (allocates a new Vec for attributes) into an Arc refcount increment.

This is a non-breaking change. SpanData.instrumentation_scope remains InstrumentationScope, not Arc<InstrumentationScope>. The instrumentation_scope() accessor still returns &InstrumentationScope via Deref.

Supersedes #3267 — thanks to @taisho6339 for the original approach and discussion.

Relates to #3368.

Note: The multi-processor on_end build-once optimization that was previously in this PR has been removed — it is superseded by the FinishedSpan approach in #3410.

Changes

  • SdkTracer.scope: InstrumentationScopeArc<InstrumentationScope> (internal field, not public API)
  • Added test for multi-processor span delivery with instrumentation_scope propagation assertions

@bryantbiggs bryantbiggs requested a review from a team as a code owner February 20, 2026 18:07
@codecov
Copy link

codecov bot commented Feb 20, 2026

Codecov Report

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

Additional details and impacted files
@@          Coverage Diff          @@
##            main   #3378   +/-   ##
=====================================
  Coverage   83.1%   83.2%           
=====================================
  Files        128     128           
  Lines      24877   24905   +28     
=====================================
+ Hits       20688   20724   +36     
+ Misses      4189    4181    -8     

☔ View full report in Codecov by Sentry.
📢 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.

…ssor

SdkTracer now wraps InstrumentationScope in Arc internally, which makes
tracer cloning significantly cheaper (pointer bump instead of deep
clone with attribute Vec allocation).

The multi-processor span export path is also optimized: instead of
calling build_export_data() per processor (which clones the scope from
the tracer each time), SpanData is now built once and cloned for each
additional processor. The last processor receives the original without
a clone.

This is a non-breaking change — SpanData's public
instrumentation_scope field remains InstrumentationScope (not Arc).

Supersedes open-telemetry#3267, thanks to @taisho6339 for the original approach.
Verifies that when multiple span processors are configured, all
processors receive the span data with correct name and attributes.
Use split_last() instead of index tracking for the multi-processor
on_end loop. Removes unnecessary break statement and makes the
clone-for-rest, move-for-last intent explicit.

Fix changelog entry to describe what actually changed without
overstating the scope clone savings.
@bryantbiggs bryantbiggs force-pushed the perf/avoid-scope-clone-per-processor branch from f584bc2 to 8eea322 Compare March 6, 2026 02:00
…rtions

Verify that instrumentation_scope is correctly propagated to all
processors in both the clone (rest) and move (last) paths. Also
clarify changelog wording.
@scottgerring
Copy link
Member

@bantonsson this seems like something you'd have good insight on!

Copy link
Contributor

@bantonsson bantonsson left a comment

Choose a reason for hiding this comment

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

I think the Arc<...> change is good to merge, and the other optimization as well depending on how far out #2962 is

for processor in rest {
processor.on_end(export_data.clone());
}
last.on_end(export_data);
Copy link
Contributor

Choose a reason for hiding this comment

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

This optimization will be redundant after #2962 is revived and merged, but I don't know the state of that /cc @paullegranddc

It would be great if you could take a look at #2962 and come with input @bryantbiggs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the pointer @bantonsson. I took a look at #2962 — it was approved by @scottgerring and @TommyCpp back in May/June 2025 but stalled waiting for the next breaking-change release window and has since gone stale (merge conflicts, ~10 months behind main).

I've rebased it onto current main and opened #3410 to supersede it, preserving @paullegranddc's authorship on the core commit. The remaining review feedback (CHANGELOG entry) has been addressed.

For this PR (#3378), I've removed the multi-processor on_end build-once optimization since it's redundant with the FinishedSpan approach in #3410. What remains here is just the Arc<InstrumentationScope> change in SdkTracer, which is independent — it avoids deep cloning on span creation, not on_end.

@bryantbiggs bryantbiggs changed the title perf(sdk): store InstrumentationScope in Arc to avoid clone per processor perf(sdk): store InstrumentationScope in Arc for cheaper tracer clones Mar 6, 2026
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.

3 participants