-
Notifications
You must be signed in to change notification settings - Fork 740
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
support for unique_ptr and significant speedups for shared_ptr vs returning const& to internal ptr_ field #651
Comments
oops, those were the results WITH the change to return const &. Results without the change:
This is compiled on my mac with clang 5
Also, as a note to those who may not be familiar with google benchmark, benchmark::DoNotOptimize simply stops the call from being completely optimized away due to the optimizer knowing that the code does absolutely nothing in the grand scheme of things. It does NOT stop the resulting code from being optimized in any other way. As evidence, I present timings from a -O0 run:
|
From the Core Guidelines: http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#note-61
|
ping? |
Your code calls
Here is some code that I tested:
Edit 1: added |
I'm not seeing the first form work: https://godbolt.org/g/MWu4Uq Also, the two forms aren't the same. unique_ptr<not_null<T*>> has the unique_ptr managing the not_null. When deleting a not_null, the underlying object isn't destroyed because not_null is not an owning pointer. A not_null<unique_ptr<T*>> will destroy the underlying object when the unique_ptr stored in the not_null is destroyed. At least I think that's how it would work, but it doesn't compile for me so I'm just trying to think through it.
Looks like I oopsed that one. I will update. |
I don't know, what you tested, but unless I'm missing something it was certainly not the code you posted. Aside from the snippet afaik being syntactically invalid c++ code, the first version doesn't make any sense and should not compile (or at least trigger a runtime assert) |
updated code to pass with a nnsp ..instead of a sp misnamed as an nnsp:
However, the more interesting case, passing both by reference, gives not a 2x drop in speed but a 25x drop in speed using nnsp:
Benchmark calling a function taking a SomeStruct * also shows the same 25X+ slowdown when using a not_null<shared_ptr<>> vs a plain shared_ptr<> These timings aren't impacted by the |
This is a very good point. I'll need to double check if I use that in my code. And it makes me a bit confused about what the correct way is to use not_null and unique_ptr together. BTW, I updated my code. It compiled with VS2017 15.6.4 but does not compile with the latest MSVC on godbolt (and not with gcc). |
@beinhaerter: I think it would be helpful, if you could post the actual code snippet (including main function) that you are using. Also, where did you get your gsl version from (github, vcpkg, nuget ...)? In any case, as far as I understand, your solution will end up with the worst of both worlds:
And by the way,
should actually be
which is currently not possible, but would be with the changes proposed by xaxxon. |
@MikeGitb I updated my sample, it is now an MCVE. I had to change the access to p2 because it did not compile on MSVC 15.6.4. |
@beinhaerter: Sorry, my bad. I thought p2 was |
@beinhaerter your code still has double heap allocation: std::make_unique<gsl::not_null<C*>>(new C()) first allocates and constructs a C on the heap (new C()), then make_unique allocates and constructs a gsl::not_null<C*> on the heap. In the end you have a unique_ptr pointing to a not_null, itself pointing to the C instance, and you have no guarantee that the unique_ptr is not null. |
@MikeGitb No, my bad. Copy-and-paste problem. not_null required C*, unique_ptr should have been C, not C*. I'll not change the sample again.
That's what I believe now, too. Maybe it would be good not only to have the static_assert(std::is_assignable<T&, std::nullptr_t>::value, "T cannot be assigned nullptr."); but also a check that the not_null type is not a managed pointer? I am not sure if this can be handled directly in GSL without touching the STL. The following code at least takes away everything from unique/shared_ptr that makes it a managed pointer but I bet there are better ways to do it.
|
At this point it's been a couple weeks and I haven't seen anyone raise any concerns, so should I make a PR? The previous PR that was requested (in a different issue) still hasn't been merged, though, so I'm not sure what's going on. |
I think you should make the PR. As this seems to be just a side project, it can sometimes take a long time until PRs get merged. |
I think I updated the pull request correctly. #650 now it has the change to remove the unnecessary null check as well as allowing non-copyable pointers. |
I've gone through the issue list and I think I made the PR so it will auto-resolve the related existing issues when it is accepted. |
closing all my issues/PRs because GSL isn't at the maturity level of a library I can depend on. |
allows support of supporting std::unique_ptr based on @xaxxon's work fixes microsoft#89 fixes microsoft#550 fixes microsoft#651
allows support of supporting std::unique_ptr based on @xaxxon's work fixes microsoft#89 fixes microsoft#550 fixes microsoft#651
allows support of supporting std::unique_ptr based on @xaxxon's work fixes microsoft#89 fixes microsoft#550 fixes microsoft#651
allows support of supporting std::unique_ptr based on @xaxxon's work fixes microsoft#89 fixes microsoft#550 fixes microsoft#651
If get(), operator->, and operator T(), are changed from returning a copy to returning a T const &, not_null gets support for unique_ptr (and any other move-only pointer-like object) as well as drastic speedups with pointer-like types with expensive copy constructors, like shared_ptr.
However, this means that a not-aware but potentially not malicious user could const cast the const away from the reference and change the internal value. With the recent discussion to remove the per-access null check, this would allow nulls to be returned from not_null without jumping through massive hoops.
The speed benefit to shared_ptr is drastic. In the microbenchmark code (listed below), having a not_null<shared_ptr> doubles the time to access data through it in a "getter"-type function, from 20 microseconds to 40 microseconds even when fully inline-able by the optimizer, since the reference count changes cannot be optimized out. This type of cost is associated with any non-trivial copy constructor, not just shared_ptr. With the proposed change, there is no additional cost for shared_ptr since no copy is made.
As for unique_ptr, I find the use case somewhat compelling for making sure you don't forget to allocate a pointer to it, when you don't intend for it to ever be empty. Of potentially limited use, it also stops it from being moved out of, since only a const version of the unique_ptr can ever be obtained. Without the concept of a "destructive move" in c++, there's no way to say that it's ok for the contained pointer to become null because it will never be accessed again. However, this doesn't mean there is no use case for this, just that it may be smaller than one might anticipate.
There also may be ways to achieve these goals with a more complex implementation of not_null involving much more TMP and making all the comparison operators friend functions. So I guess I'm curious what other people's thoughts are on speed versus implementation simplicity versus more heavily guaranteed safety against well-intentioned but clearly incorrect use of not_null.
My vote, as evidenced by the hacked up version of not_null I actually use is to return a const & (and I intentionally violate the contract sometimes to move out of it).
results:
The text was updated successfully, but these errors were encountered: