Skip to content

Conversation

@Reneg973
Copy link
Contributor

Fixes # (issue)
non, just improvements

Changes

non-API changes in Context and nostd::shared_ptr

Please provide a brief description of the changes here.

  • may reduce sizeof(nostd::shared_ptr) and therefore sizeof(Context), if sizeof(std::shared_ptr) == 16 instead of 32
  • prevent dynamic allocation in case of short keys (SSO)
  • may perform faster because of removed vtable functions

Please provide a brief description of the changes here.

For significant contributions please make sure you have completed the following items:

  • CHANGELOG.md updated for non-trivial changes
  • Unit tests have been added
  • Changes in public API reviewed

- prevent a superfluous copy of ContextValue
- replace raw char* key pointer with std::string, increases sizeof(DataList) but introduces small string optimization
- introduced rule of 0 as far as possible (copy/move constructor and assignment and also destructor can be generated by compiler)
- removed unnecessary virtual functions
- may reduce sizeof(shared_ptr) by 50% (sizeof(shared_ptr) may be just 16 bytes
- removed unused header cstdlib
- rule of 0 (removed destructor)
- replace unique_ptr.release() by std::move() to make it more clear/readable
@Reneg973 Reneg973 requested a review from a team as a code owner October 24, 2025 11:23
@Reneg973
Copy link
Contributor Author

Reneg973 commented Oct 24, 2025

missing header <string> for self sufficiency in context.h, will update it later

- removed another copy of ContextValue
- made functions not changing internal const
@codecov
Copy link

codecov bot commented Oct 24, 2025

Codecov Report

❌ Patch coverage is 92.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.01%. Comparing base (d882c6b) to head (1a8b433).

Files with missing lines Patch % Lines
api/include/opentelemetry/nostd/shared_ptr.h 75.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3713      +/-   ##
==========================================
+ Coverage   90.00%   90.01%   +0.01%     
==========================================
  Files         225      225              
  Lines        7099     7091       -8     
==========================================
- Hits         6389     6382       -7     
+ Misses        710      709       -1     
Files with missing lines Coverage Δ
api/include/opentelemetry/context/context.h 100.00% <100.00%> (ø)
api/include/opentelemetry/nostd/shared_ptr.h 96.83% <75.00%> (-3.17%) ⬇️

... and 1 file with indirect coverage changes

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

@lalitb lalitb requested a review from Copilot October 24, 2025 20:28
Copy link

Copilot AI left a comment

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 the Context and nostd::shared_ptr implementations by reducing memory overhead and eliminating dynamic memory allocations. The changes improve performance through more efficient memory management, removal of virtual function overhead, and enabling SSO (Small String Optimization) for context keys.

Key changes:

  • Removes virtual functions from shared_ptr_wrapper to eliminate vtable overhead
  • Uses std::string for context keys instead of raw char* to enable SSO and simplify memory management
  • Dynamically sizes PlacementBuffer based on shared_ptr_wrapper size to potentially reduce sizeof(shared_ptr)

Reviewed Changes

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

File Description
api/include/opentelemetry/nostd/shared_ptr.h Removes virtual functions, dynamically sizes placement buffer, adds self-assignment checks, fixes unique_ptr conversion
api/include/opentelemetry/context/context.h Replaces manual key memory management with std::string, simplifies key comparison, marks methods const-correct
.vscode/launch.json Removes IDE-specific configuration file

shared_ptr(unique_ptr<T> &&other) noexcept
{
std::shared_ptr<T> ptr_(other.release());
std::shared_ptr<T> ptr_(std::move(other));
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

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

Using std::move() on a nostd::unique_ptr will not work correctly. The original code with other.release() was correct. nostd::unique_ptr may not have a move constructor that works with std::shared_ptr, but it does have a release() method that returns the raw pointer. This change will likely cause compilation errors or incorrect behavior.

Suggested change
std::shared_ptr<T> ptr_(std::move(other));
std::shared_ptr<T> ptr_(other.release());

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

@Reneg973 Reneg973 Oct 25, 2025

Choose a reason for hiding this comment

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

I don't get this!
According to cppref(constructor 13) this constructor exists. Additionally nostd::unique_ptr implements move operations perfectly.
Also, if this would be true, then code like:

nostd::unique_ptr<T> Foo() { return new T; }
std::shared_ptr<T> ptr(Foo());

would not work.

PS: GPT-5 says everything is fine with this code

Copy link
Member

Choose a reason for hiding this comment

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

As I understand it, std::shared_ptr should not recognize nostd::unique_ptr and cannot be constructed from its rvalue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As I understand it, std::shared_ptr should not recognize nostd::unique_ptr and cannot be constructed from its rvalue.

Perfect, I was blind and naive. Thought I saw a test for it, but didn't. After writing one I directly had compiler error.

// and returns that head.
DataList(nostd::string_view key, const ContextValue &value)
: key_(key.begin(), key.end())
, value_( value)
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

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

Extra space before closing parenthesis in initializer list.

Suggested change
, value_( value)
, value_(value)

Copilot uses AI. Check for mistakes.
- Fixed formatting issues in `context.h`
- Adjusted `shared_ptr` construction from `unique_ptr`
- Added test for move construction from `nostd::unique_ptr`
@Reneg973 Reneg973 force-pushed the Context_and_shared_ptr_improvements branch from 481649a to 923bc47 Compare October 27, 2025 06:49
Copy link
Member

@marcalff marcalff left a comment

Choose a reason for hiding this comment

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

@Reneg973

Thanks for the PR.

This area is much more complex that is appears on first sight.

Both the API and the ABI here must be immutable, so that:

  • an library compiled with opentelemetry-cpp API 1.23.0 or older
  • another library compiled with opentelemetry-cpp API 1.24.0 or newer
  • all linked in an application configured with possibly yet another SDK version

all agree on what bit in the binary layout of a context or shared_ptr means, and can operate on the same data.

If we were to implement this area from scratch today, we would probably use most of the cleanup done here, but unfortunately we can not, due ABI breaking changes.

We could implement some changes only in ABI_VERSION >= 2, but this is not worth the added complexity in my opinion.

Some areas in opentelemetry-cpp are easier and less risky than others, the API is deceptive, and probably the most difficult to change, compared to contributions in the SDK itself for example.

// Creates a context object from a key and value, this will
// hold a shared_ptr to the head of the DataList linked list
Context(nostd::string_view key, ContextValue value) noexcept
Context(nostd::string_view key, const ContextValue &value) noexcept
Copy link
Member

Choose a reason for hiding this comment

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

ABI break

// contains the new key and value data. It attaches the
// exisiting list to the end of the new list.
Context SetValue(nostd::string_view key, ContextValue value) noexcept
Context SetValue(nostd::string_view key, const ContextValue &value) const noexcept
Copy link
Member

Choose a reason for hiding this comment

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

Valid and better, but ABI break

struct DataList
{
char *key_ = nullptr;
std::string key_;
Copy link
Member

Choose a reason for hiding this comment

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

ABI break

virtual ~shared_ptr_wrapper() {}

virtual void CopyTo(PlacementBuffer &buffer) const noexcept
void CopyTo(PlacementBuffer &buffer) const noexcept
Copy link
Member

Choose a reason for hiding this comment

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

Valid and better, but ABI break.

static_assert(sizeof(shared_ptr_wrapper) <= kMaxSize, "Placement buffer is too small");
struct alignas(kAlignment) PlacementBuffer
{
char data[sizeof(shared_ptr_wrapper)]{};
Copy link
Member

Choose a reason for hiding this comment

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

ABI break

Comment on lines +119 to +122
if (this == &other)
{
return *this;
}
Copy link
Member

Choose a reason for hiding this comment

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

This is valid, please post a separate PR for this fix,

@Reneg973
Copy link
Contributor Author

We could implement some changes only in ABI_VERSION >= 2, but this is not worth the added complexity in my opinion.

Well, if you decide not to integrate it, you can close this PR, which is ok, even if I disagree.
The API and ABI currently add superfluous and also noticeable runtime overhead, especially for more lightweight C++ apps or on CPUs like a Raspi Zero.
On heavily multithreaded apps: you don't use a custom private heap, which means every dynamic alloc/free blocks other threads using the global heap. Also there's a lot of shared_ptr usage. Especially the virtual inside the shared_ptr privates just adds superfluous indirections.

an library compiled with opentelemetry-cpp API 1.23.0 or older
another library compiled with opentelemetry-cpp API 1.24.0 or newer

Does this mean you want to allow use cases where an object is created with API 1.23 and pushed into 1.24? Otherwise I don't see that they influence each other.

This was just a first proposal to slim down the overhead without losing functionality.

@marcalff
Copy link
Member

We could implement some changes only in ABI_VERSION >= 2, but this is not worth the added complexity in my opinion.

Well, if you decide not to integrate it, you can close this PR, which is ok, even if I disagree.

@Reneg973

The problem is as follows:

For OPENTELEMETRY_ABI_VERSION_NO == 1, the current code is as it is, and we can not change it if this breaks the abi.

For OPENTELEMETRY_ABI_VERSION_NO >= 2, the code can be changed.

I agree it would improve the final product, but it also creates a significant effort in maintenance in the mean time, with potential regressions if things are different between abi 1 and 2. This is what I meant with added complexity.

The API and ABI currently add superfluous and also noticeable runtime overhead, especially for more lightweight C++ apps or on CPUs like a Raspi Zero.

No contest here. The question is whether this is fixable without breaking existing applications, and without too much burden.

On heavily multithreaded apps: you don't use a custom private heap, which means every dynamic alloc/free blocks other threads using the global heap.

The API code has to be header only, which excludes using a custom allocator, memory pool, or anything like it.

The SDK and exporters have more implementation freedom, but this is another area.

Also there's a lot of shared_ptr usage.

For example, see #828

This has been known for a long time, and is not trivial to fix, due to the breaking change label.

Especially the virtual inside the shared_ptr privates just adds superfluous indirections.

That sounds fixable in ABI_VERSION 2.

an library compiled with opentelemetry-cpp API 1.23.0 or older
another library compiled with opentelemetry-cpp API 1.24.0 or newer

Does this mean you want to allow use cases where an object is created with API 1.23 and pushed into 1.24? Otherwise I don't see that they influence each other.

An application or a library is instrumented by invoking the API only.
Say a library is compiled against API 1.23.
Calls to create a span (to instrument traces) can land either in the no-op implementation, or in the SDK.

The SDK is what is configured in the main() for the overall application. It could be SDK 1.25, which implements the API.

The problem here is that SDK 1.25 has to work not just with API 1.25, which is in the same code base in the same git label, but also with API 1.23. There is no CI build to verify this, so every time any code under api/include is touched, it needs extra review.

Long story short, if a library is instrumented (with API 1.23) and ships a binary package, linking this library in an application that uses the SDK 1.25 should not require whoever provided the library to produce and ship again another package (built with API 1.25).

This was just a first proposal to slim down the overhead without losing functionality.

And this is appreciated.

Overall, ABI 1 and 2 are already different, and CI is already covering both, so having a few more ifdef to fix existing issues in ABI 2 only is possible.

Ultimately, this work is needed, and the sooner ABI 2 can be cleaned up and ready, the sooner we can deprecate ABI 1.

The pitfall to avoid is to start making changes, then get distracted and delayed, to end up with ABI 1 still in use, ABI 2 very different, better but still not complete, with diverging code.

Considering all the cleanup needed anyway due to recent clang-tidy findings in #3762, it looks like opentelemetry-cpp can not postpone this cleanup forever, so we might as well start it.

Do you think the PR can be adjusted to change only ABI_VERSION 2, and pass all ci ? It will be considered.

@Reneg973
Copy link
Contributor Author

Your explanations are very appreciated, thanks.

Do you think the PR can be adjusted to change only ABI_VERSION 2, and pass all ci ? It will be considered.

ok, that's the kind of statement I waited for (getting a hint, what to do that this PR could be approved).
I'll prepare one, maybe not tomorrow, but in the next days.

Reneg973 added a commit to Reneg973/opentelemetry-cpp that referenced this pull request Nov 25, 2025
Reneg973 added a commit to Reneg973/opentelemetry-cpp that referenced this pull request Nov 25, 2025
Reneg973 added a commit to Reneg973/opentelemetry-cpp that referenced this pull request Nov 26, 2025
Reneg973 added a commit to Reneg973/opentelemetry-cpp that referenced this pull request Nov 29, 2025
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.

4 participants