Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a method (and benchmark) to Context to speed up active span determination by 7x #1140

Merged
merged 5 commits into from
Jul 6, 2023

Conversation

shaun-cox
Copy link
Contributor

@shaun-cox shaun-cox commented Jul 4, 2023

In my use case for tracing instrumentation, I only want to pay the costs of creating a new context with a new span if the current context contains an active span, but I find the current cost associated with simply checking for this condition to be unacceptably high.

Simply calling Context::current().has_active_span() does the following:

  • clones the current context (thus cloning the contained HashMap, copying type ids and incrementing Arcs of the values)
  • if the context does contain a span, atomically increments the Arc value in the HashMap
  • invokes has_active_span() which checks to see if entry exists
  • drops the cloned context (thus invoking drop for the contained HashMap, decrementing Arcs of any values)

Changes

I added a generic method to Context called map_current which, instead of cloning, allows the caller to pass a lambda to be invoked with a reference to the current context (if any) and returns a value of some type of their choosing.

With this facility, checking for an active span can look like:

Context::map_current(|cx| cx.has_active_span())

I added a benchmark to demonstrate the cost difference, and it is substantial.

violin

I think this new API feels like a bit of a hack compared with the completely idiomatic way of getting the current context in the first place, which would more resemble:

impl Context {
   pub fn current() -> Option<&Context>
...

were it not for the obvious lifetime issue of the returned reference. So I took the standard map approach of using the lambda's scope to work with the valid reference to the current context and let the caller algebraically build up whatever construct they'd like to return.

I also think there is this tension between the OpenTelemetry spec, which seems to bias/favor towards full-blown "empty" values (e.g. Context::default() or NoopSpan) to represent something that is "absent", rather than promoting the powerful facilities of the particular language binding (e.g. Option<Context> or Option<Span>). This is related to #1089, for example.

I would like to see us continue to improve the idiomatic aspects of this Rust binding of the OpenTelemetry API with the goal of having this binding be the absolute highest performing one out there among them all. I think that embracing algebraic types more is key to achieving this.

Merge requirement checklist

  • CONTRIBUTING guidelines followed
  • Unit tests added/updated (if applicable)
  • Appropriate CHANGELOG.md files updated for non-trivial, user-facing changes
  • Changes in public API reviewed (if applicable)

@shaun-cox shaun-cox requested a review from a team as a code owner July 4, 2023 16:13
- no need to clone current context for each processor
- use Context::map_current instead, outside loop, to
  compute optional TraceContext once
@djc
Copy link
Contributor

djc commented Jul 4, 2023

A 7x win sounds impressive!

were it not for the obvious lifetime issue of the returned reference.

I don't have enough context on the obviousness of the lifetime issue, can you elaborate a bit?

I would like to see us continue to improve the idiomatic aspects of this Rust binding of the OpenTelemetry API with the goal of having this binding be the absolute highest performing one out there among them all. I think that embracing algebraic types more is key to achieving this.

Presumably everyone following along would not be surprised to hear that I wholeheartedly agree with this.

@codecov
Copy link

codecov bot commented Jul 4, 2023

Codecov Report

Patch coverage: 71.8% and project coverage change: -0.1 ⚠️

Comparison is base (821a7f2) 49.8% compared to head (29d2ec0) 49.7%.

Additional details and impacted files
@@           Coverage Diff           @@
##            main   #1140     +/-   ##
=======================================
- Coverage   49.8%   49.7%   -0.1%     
=======================================
  Files        171     171             
  Lines      20254   20284     +30     
=======================================
+ Hits       10092   10100      +8     
- Misses     10162   10184     +22     
Impacted Files Coverage Δ
opentelemetry-sdk/src/logs/log_emitter.rs 0.0% <0.0%> (ø)
...lemetry-api/src/propagation/text_map_propagator.rs 69.2% <50.0%> (ø)
opentelemetry-api/src/context.rs 92.4% <100.0%> (+0.6%) ⬆️
opentelemetry-api/src/trace/tracer.rs 45.6% <100.0%> (ø)
opentelemetry-sdk/src/propagation/trace_context.rs 98.4% <100.0%> (ø)
opentelemetry-sdk/src/trace/tracer.rs 93.5% <100.0%> (+<0.1%) ⬆️

... and 3 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@shaun-cox
Copy link
Contributor Author

I don't have enough context on the obviousness of the lifetime issue, can you elaborate a bit?

Sure! The current context is owned by a std::thread::LocalKey and that only provides a reference to what it owns via a closure. So its not really feasible to obtain something like an Option<&'static Context> by calling a function.

@lalitb
Copy link
Member

lalitb commented Jul 6, 2023

+1 from logs prospective, I think this should improve logging performance for scenarios where we log outside of active span.

Copy link
Member

@jtescher jtescher left a comment

Choose a reason for hiding this comment

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

Looks reasonable to me, the risk of others misusing this api by calling map_current in a way that causes your app to panic through the double borrow at runtime is probably worth it to avoid the clones in performance sensitive cases.

@jtescher jtescher merged commit 0bf18ce into open-telemetry:main Jul 6, 2023
12 checks passed
@shaun-cox shaun-cox deleted the context_bench branch July 6, 2023 13:50
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.

None yet

4 participants