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

[core] Pass TString by value to TNamed ctor #15469

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ChristianTackeGSI
Copy link
Collaborator

This Pull request:

Passing by const-reference can mean two constructions (once on the caller, once in the ctor using the TString copy ctor).

Instead passing by value and using move semantics in the ctor can reduce this to one construction on the caller and one move in the ctor.

Checklist:

  • tested changes locally
  • updated the docs (if necessary)

fixes: #15434

Copy link

github-actions bot commented May 9, 2024

Test Results

    11 files      11 suites   2d 16h 17m 3s ⏱️
 2 645 tests  2 645 ✅ 0 💤 0 ❌
27 455 runs  27 455 ✅ 0 💤 0 ❌

Results for commit 2bafc6e.

♻️ This comment has been updated with latest results.

TNamed(const char *name, const char *title) : fName(name), fTitle(title) { }
TNamed(const TString &name, const TString &title) : fName(name), fTitle(title) { }
TNamed(TString name, TString title) : fName(std::move(name)), fTitle(std::move(title)) {}
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't

Suggested change
TNamed(TString name, TString title) : fName(std::move(name)), fTitle(std::move(title)) {}
TNamed(const &TString name, const &TString title) : fName(name), fTitle(title) {}
TNamed(TString &&name, const TString &title) : fName(std::move(name)), fTitle(title) {}
TNamed(const TString &name, TString &&title) : fName(name), fTitle(std::move(title)) {}
TNamed(TString &&name, TString &&title) : fName(std::move(name)), fTitle(std::move(title)) {}

be even more performant? (Albeit annoying :) )

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Please take a look at the discussion in #15434. Especially https://www.cppstories.com/2018/08/init-string-member/, which analyzes nearly every combination.

A const-reference is not any more performant then a plain TString (assuming move is very cheap).

Copy link
Contributor

Choose a reason for hiding this comment

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

Has this been tested? Either by looking at the compiler output of some "real" code or by benchmarking?
Whenever performance and compiler optimizations are involved it's always best to verify the theoretical claims since it's very hard to guess correctly.

Copy link
Member

Choose a reason for hiding this comment

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

Especially cppstories.com/2018/08/init-string-member, which analyzes nearly every combination.

Actually they seems have left the case of the rvalue reference/universal forwarding as an exercise to the reader ("For now, I’d like to stop here and don’t go into details. ... " ) ;)

Copy link
Member

@pcanal pcanal May 10, 2024

Choose a reason for hiding this comment

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

Whenever performance and compiler optimizations are involved it's always best to verify the theoretical claims since it's very hard to guess correctly.

Agreed. Also note that TString does have a "Small String Optimization (SSO)" which may or may not change things.

@ChristianTackeGSI
Copy link
Collaborator Author

@guitargeek should I rebase or something?

@guitargeek
Copy link
Contributor

I was just closing and opening to refresh the test results. I'm baffled that there are failures in what seems like a trivial PR!

@guitargeek
Copy link
Contributor

Maybe we uncovered a problem with the TString move constructor? Any intuition, @pcanal?

@ChristianTackeGSI
Copy link
Collaborator Author

Rebased, so that the tests run against the latest version. Maybe it''s getting better?

@pcanal
Copy link
Member

pcanal commented Jun 4, 2024

Maybe we uncovered a problem with the TString move constructor?

It's implementation is odd looking :(, so yeah I think this is the issue ... let me do a quick checks

@pcanal
Copy link
Member

pcanal commented Jun 4, 2024

It's implementation is odd looking :(, so yeah I think this is the issue ... let me do a quick checks

and it actually seems to be working ... so now I'll try to reproduce the failure.

@pcanal
Copy link
Member

pcanal commented Jun 4, 2024

I can reproduce the failure locally.

@@ -33,9 +33,9 @@ class TNamed : public TObject {
TString fTitle; //object title

public:
TNamed(): fName(), fTitle() { }
TNamed() = default;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
TNamed() = default;
// The following is *not* the default implementation (i.e. do not use `= default`.
// Default implementation can do 'more' than expected. In this case it reset all the
// data member of `TNamed` and more importantly its base class `TObject`.
// TObject's implementation of the `IsOnHeap` bit requires the memory occupied
// by `TObject::fUniqueID` to *not* be reset between the execution of `TObject::operator new`
// and the `TObject` constructor (Finding the magic pattern there is how we can determine
// that the object was allocated on the heap.
TNamed() : fName(), fTitle() { }

Copy link
Member

@pcanal pcanal left a comment

Choose a reason for hiding this comment

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

@ChristianTackeGSI
Copy link
Collaborator Author

I can add those comments to my PR. I am wondering, whether TNamed is the only affected class?

If not, then this warning should be put in some central place instead of scattered around here?

If I get this correctly, it could also affect user classes? I have opened a topic on the forum about this to discuss this outside this PR.

@pcanal
Copy link
Member

pcanal commented Jun 5, 2024

I can add those comments to my PR. I am wondering, whether TNamed is the only affected class?

That is a good question.

If not, then this warning should be put in some central place instead of scattered around here?

My guess it that it would likely affect at least all classes that derives directly from TObject.

@pcanal
Copy link
Member

pcanal commented Jun 5, 2024

So this problem has been known for 5 years :( #4320 but we manage to indeed lose track of it. That PR used the following more concise pattern:

TView() {} // NOLINT: not allowed to use = default because of TObject::kIsOnHeap detection, see ROOT-10300

where both the NOLINT is indeed useful to avoid spurious tool recommendation and the wording was clear but should probably be updated (ROOT-10300 is a ticket number in the JIRA instance which is now read-only)

It would be good to add some wording in the TObject documentation.

@ChristianTackeGSI
Copy link
Collaborator Author

So please suggest a concise wording for this PR and I will change it. I'd probably go for

  TNamed() {}   // your-wording

then.

core/base/inc/TNamed.h Outdated Show resolved Hide resolved
@pcanal
Copy link
Member

pcanal commented Jun 5, 2024

For the TObject documentation I would add something along the line of:

Classes derived from `TObject` can not use the `= default` syntax for their constructor as some compilers implement optimizations that prevents the `TObject::kIsOnHeap` detection mechanism from working properly.

@ChristianTackeGSI
Copy link
Collaborator Author

For the TObject documentation I would add something along the line of:

Classes derived from `TObject` can not use the `= default` syntax for their constructor as some compilers implement optimizations that prevents the `TObject::kIsOnHeap` detection mechanism from working properly.

Can we move this into another PR? (I would suggest you opening it), because it's still not clear whether the main target of the current PR is actually desired or not.

And TBH, I don't have the resources to perform a proper benchmark, nor do I have good other arguments why the proposed style is better (for example more readable, more maintainable, etc).

Passing by const-reference can mean two constructions (once
on the caller, once in the ctor using the TString copy
ctor).

Instead passing by value and using move semantics in the
ctor can reduce this to one construction on the caller and
one move in the ctor.

As well simplify default constructor by defaulting it.

Co-Authored-By: @pcanal
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.

Consider passing TString by value to TNamed ctor
4 participants