-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
BTreeMap::entry: Avoid allocating if no insertion #92962
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @m-ou-se (or someone else) soon. Please see the contribution instructions for more information. |
r? rust-lang/libs |
Good idea, but right now, I think an insert would reallocate the root node of an emptied map (a map with an empty root node, a map whose elements have been individually removed). I would replace |
let mut root = NodeRef::new_leaf(); | ||
let mut leaf_node = root.borrow_mut(); | ||
leaf_node.push(self.key, value); | ||
let out_ptr: *mut V = leaf_node.last_kv().into_val_mut() as *mut V; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would let push
return a &mut V
, which is already returned by the last write
in its body. And the first *mut V
seems pretty superfluous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The *mut V
comes from Handle::insert_fit
, then forwarded by Handle::insert
, and then reborrowed by Entry::insert
. For this special case (inserting into a new map), I agree with you that returning &mut V
directly is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For clarity, with "the first *mut V" I meant the type annotation, in addition to the "as" cast. I was too lazy to look up the right terms (it's not type ascription or declaration); and I'm still too lazy to figure out "as" - it's a "primitive cast", says the book, but "cast" appears only twice elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do have a physical book (O'Reilly's Programming Rust): it says the "as operator" converts, doesn't even mention casting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I messed up the *mut V
you mentioned with other places and I thought you meant the conversion from &mut V
to *mut V
and then back to &mut V
. So there has been another version discussed at #92962 (comment).
For clarity, with "the first *mut V" I meant the type annotation, in addition to the "as" cast.
Then I realised you meant the two *mut V
right here in this same statement.
And the first *mut V seems pretty superfluous.
I do agree with you. Thanks for pointing out that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see now that it makes little sense to let insert_fit
(on leaf nodes) return &mut V
, because it doesn't live long enough. It cannot return &'a mut V
, because the return value of assume_init_mut
doesn't live long enough and the borrow checker won't allow that. It cannot consume self
either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so. assume_init_mut
borrows from self.node.val_area_mut(self.idx)
, which borrows from self.node
. Since self
is passed by reference, it is no problem to return a &mut V
which lives as long as &mut self
(both of which lifetimes are eliminated).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I tried to clarify the first version of the comment and pasted into the wrong sentence. Yes, there's no problem letting it return &mut V
, and I think that's slightly better, but even then, we need a &'a mut V
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I tried at https://github.com/ssomers/rust/tree/btree_prune_insert_2 to pass the &'a mut V
directly back to the caller, and have one less case where the conversion to *mut V
happens, but it's not worth the trouble. So instead I settled on the slight simplification #94699.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InsertResult::Fit(unsafe { &mut *(val_ref as *mut _) })
Oh, I think it's even more tricky than the current version. I really thought it was totally safe until I see this unsafe
block. This just splits up a single unsafe
block (at the end of Entry::insert
) to two, and one of which is hidden inside (in the node
module).
Anyway, #94699 looks much better.
6db0141
to
bbbee0a
Compare
This comment has been minimized.
This comment has been minimized.
bbbee0a
to
e65ea46
Compare
This comment has been minimized.
This comment has been minimized.
e65ea46
to
d71a81e
Compare
This comment has been minimized.
This comment has been minimized.
e464b8b
to
0fc1659
Compare
I'm afraid I cannot fully understand what you said. As I observed, even before this PR, inserting into an emptied allocated map via
I replaced |
With "right now" I meant that in the first form of your PR, I think it would reallocate the root of an already allocated empty map. And implicitly that it didn't do that before your PR.
It has pros and cons. It used to be impossible, when the unit tests where filed as integration tests, because there's no way for outside clients to see if an empty map is allocated or not. So the question hasn't come up. But that wasn't what my comment was about.
Like you did in the second version of your PR: don't use Only now I realize the name |
I find
There is a common reason to use
You mentioned the naming of |
0fc1659
to
23d41ab
Compare
This comment has been minimized.
This comment has been minimized.
23d41ab
to
d9ed640
Compare
I made |
On second thought, the function does/did operate on an Option, so that thing does contain a root. |
03169f5
to
af4721f
Compare
928e6c9
to
97a0083
Compare
☔ The latest upstream changes (presumably #94761) made this pull request unmergeable. Please resolve the merge conflicts. |
97a0083
to
2c3c891
Compare
✌️ @ssomers can now approve this pull request |
@bors r+ |
📌 Commit 2c3c891 has been approved by |
⌛ Testing commit 2c3c891 with merge 078166e30b26dd26beeb1a38234f2f748109cd96... |
💔 Test failed - checks-actions |
The job Click to see the possible cause of the failure (guessed by this bot)
|
@bors retry |
☀️ Test successful - checks-actions |
Finished benchmarking commit (c7ce69f): comparison url. Summary: This benchmark run did not return any relevant results. 5 results were found to be statistically significant but too small to be relevant. If you disagree with this performance assessment, please file an issue in rust-lang/rustc-perf. @rustbot label: -perf-regression |
This PR allows the
VacantEntry
to borrow from an empty tree with no root, and to lazily allocate a new root node when the user calls.insert(value)
.