Skip to content

Bundle and document 6 BTreeMap navigation algorithms - #67073

Merged
bors merged 1 commit into
rust-lang:masterfrom
ssomers:btree_navigation
Jan 31, 2020
Merged

Bundle and document 6 BTreeMap navigation algorithms#67073
bors merged 1 commit into
rust-lang:masterfrom
ssomers:btree_navigation

Conversation

@ssomers

@ssomersssomers commented Dec 6, 2019

Copy link
Copy Markdown
Contributor
  • Expose a function to step through trees, without necessarily extracting the KV pair, that helps future operations like drain/retain (as demonstrated in this drain_filter implementation)
  • Also aligns the implementation of the 2 x 3 iterators already using such navigation:
    • Delay the moment the K,V references are plucked from the tree, for the 4 iterators on immutable and owned maps, just for symmetry. The same had to be done to the two iterators for mutable maps in fix overlapping references in BTree #58431.
    • Always explicitly use ptr::read to derive two handles from one handle. While the existing implementations for immutable maps (i.e. Range::next_unchecked and Range::next_back_unchecked) rely on implicit copying. There's no change in unsafe tags because these two functions were already (erroneously? prophetically?) tagged unsafe. I don't know whether they should be tagged unsafe. I guess they should be for mutable and owned maps, because you can change the map through one handle and leave the other handle invalid.
    • Preserve the way two handles are (temporarily) derived from one handle: implementations for immutable maps (i.e. Range::next_unchecked and Range::next_back_unchecked) rely on copying (implicitly before, explicitly now) and the others do ptr::read.
    • I think the functions that support iterators on immutable trees (i.e. Range::next_unchecked and Range::next_back_unchecked) are erroneously tagged unsafe since you can already create multiple instances of such ranges, thus obtain multiple handles into the same tree. I did not change that but removed unsafe from the functions underneath.

Tested with miri in liballoc/tests/btree, except those that should_panic.

cargo benchcmp the best of 3 samples of all btree benchmarks before and after this PR:

name old1.txt ns/iter new2.txt ns/iter diff ns/iter diff % speedup
btree::map::find_rand_100 17 17 0 0.00% x 1.00
btree::map::find_rand_10_000 57 55 -2 -3.51% x 1.04
btree::map::find_seq_100 17 17 0 0.00% x 1.00
btree::map::find_seq_10_000 42 39 -3 -7.14% x 1.08
btree::map::first_and_last_0 14 14 0 0.00% x 1.00
btree::map::first_and_last_100 36 37 1 2.78% x 0.97
btree::map::first_and_last_10k 52 52 0 0.00% x 1.00
btree::map::insert_rand_100 34 34 0 0.00% x 1.00
btree::map::insert_rand_10_000 34 34 0 0.00% x 1.00
btree::map::insert_seq_100 46 46 0 0.00% x 1.00
btree::map::insert_seq_10_000 90 89 -1 -1.11% x 1.01
btree::map::iter_1000 2,811 2,771 -40 -1.42% x 1.01
btree::map::iter_100000 360,635 355,995 -4,640 -1.29% x 1.01
btree::map::iter_20 39 42 3 7.69% x 0.93
btree::map::iter_mut_1000 2,662 2,864 202 7.59% x 0.93
btree::map::iter_mut_100000 336,825 346,550 9,725 2.89% x 0.97
btree::map::iter_mut_20 40 43 3 7.50% x 0.93
btree::set::build_and_clear 4,184 3,994 -190 -4.54% x 1.05
btree::set::build_and_drop 4,151 3,976 -175 -4.22% x 1.04
btree::set::build_and_into_iter 4,196 4,155 -41 -0.98% x 1.01
btree::set::build_and_pop_all 5,176 5,241 65 1.26% x 0.99
btree::set::build_and_remove_all 6,868 6,903 35 0.51% x 0.99
btree::set::difference_random_100_vs_100 721 691 -30 -4.16% x 1.04
btree::set::difference_random_100_vs_10k 2,420 2,411 -9 -0.37% x 1.00
btree::set::difference_random_10k_vs_100 54,726 52,215 -2,511 -4.59% x 1.05
btree::set::difference_random_10k_vs_10k 164,384 170,256 5,872 3.57% x 0.97
btree::set::difference_staggered_100_vs_100 739 709 -30 -4.06% x 1.04
btree::set::difference_staggered_100_vs_10k 2,320 2,265 -55 -2.37% x 1.02
btree::set::difference_staggered_10k_vs_10k 68,020 70,246 2,226 3.27% x 0.97
btree::set::intersection_100_neg_vs_100_pos 23 24 1 4.35% x 0.96
btree::set::intersection_100_neg_vs_10k_pos 28 29 1 3.57% x 0.97
btree::set::intersection_100_pos_vs_100_neg 24 24 0 0.00% x 1.00
btree::set::intersection_100_pos_vs_10k_neg 28 28 0 0.00% x 1.00
btree::set::intersection_10k_neg_vs_100_pos 27 27 0 0.00% x 1.00
btree::set::intersection_10k_neg_vs_10k_pos 30 29 -1 -3.33% x 1.03
btree::set::intersection_10k_pos_vs_100_neg 27 28 1 3.70% x 0.96
btree::set::intersection_10k_pos_vs_10k_neg 29 29 0 0.00% x 1.00
btree::set::intersection_random_100_vs_100 592 572 -20 -3.38% x 1.03
btree::set::intersection_random_100_vs_10k 2,271 2,269 -2 -0.09% x 1.00
btree::set::intersection_random_10k_vs_100 2,301 2,333 32 1.39% x 0.99
btree::set::intersection_random_10k_vs_10k 147,879 150,148 2,269 1.53% x 0.98
btree::set::intersection_staggered_100_vs_100 622 632 10 1.61% x 0.98
btree::set::intersection_staggered_100_vs_10k 2,101 2,032 -69 -3.28% x 1.03
btree::set::intersection_staggered_10k_vs_10k 60,341 61,834 1,493 2.47% x 0.98
btree::set::is_subset_100_vs_100 417 426 9 2.16% x 0.98
btree::set::is_subset_100_vs_10k 1,281 1,324 43 3.36% x 0.97
btree::set::is_subset_10k_vs_100 2 2 0 0.00% x 1.00
btree::set::is_subset_10k_vs_10k 41,054 41,612 558 1.36% x 0.99

r? cuviper

@rust-highfive

Copy link
Copy Markdown
Contributor

r? @kennytm

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfiverust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Dec 6, 2019
@ssomers

Copy link
Copy Markdown
ContributorAuthor

Let me try that again...

r? @cuviper

@rust-highfiverust-highfive assigned cuviper and unassigned kennytmDec 6, 2019
@ssomersssomers changed the title BTreeMap/BTreeSet share, expose and align 6 tree navigation algorithmsShare, expose and align 6 BTreeMap navigation algorithmsDec 6, 2019
@ssomersssomers changed the title Share, expose and align 6 BTreeMap navigation algorithmsExpose and align 6 BTreeMap navigation algorithmsDec 6, 2019
@ssomers

Copy link
Copy Markdown
ContributorAuthor

Checking again with miri (properly or with newer version), there are plenty of leaks.

@ssomersssomers closed this Dec 13, 2019
@cuviper

Copy link
Copy Markdown
Member

Sorry I lost the review on this.

Checking again with miri (properly or with newer version), there are plenty of leaks.

Having closed this PR, do you intend to address those issues and reopen it?

@ssomers

Copy link
Copy Markdown
ContributorAuthor

Sure, reopening is my intent. But it doesn't look as straightforward as it did hours ago.

@ssomers

This comment has been minimized.

@ssomersssomers reopened this Dec 14, 2019
@ssomersssomers changed the title Expose and align 6 BTreeMap navigation algorithmsExpose and align 4 BTreeMap navigation algorithmsDec 14, 2019
@ssomers

Copy link
Copy Markdown
ContributorAuthor

Meanwhile miri says drain_filter building on this PR also works fine

@ssomers

This comment has been minimized.

@bors

bors commented Dec 23, 2019

Copy link
Copy Markdown
Collaborator

☔ The latest upstream changes (presumably #67540) made this pull request unmergeable. Please resolve the merge conflicts.

@ssomers

ssomers commented Dec 28, 2019

Copy link
Copy Markdown
ContributorAuthor

Last 2 commits were only to rebase on a recent master branch (which includes code reformatting for the 1st commit)

@ssomers

This comment has been minimized.

@ssomers

This comment has been minimized.

@ssomersssomers closed this Dec 29, 2019
@ssomersssomers reopened this Dec 29, 2019
@ssomersssomers changed the title Expose and align 4 BTreeMap navigation algorithmsExpose and align 6 BTreeMap navigation algorithmsDec 29, 2019
@ssomers

This comment has been minimized.

@ssomers

This comment has been minimized.

Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this pull request Jan 19, 2020
…, r=Mark-Simulacrum
Add more BTreeMap/BTreeSet benchmarks regarding iteration
Serving rust-lang#67073 or other developments
@Mark-Simulacrum

Copy link
Copy Markdown
Member

@cuviper Do you plan to pick up the review here, or do you not expect to have time to do so?

bors added a commit that referenced this pull request Jan 21, 2020
…mulacrum
Add more BTreeMap/BTreeSet benchmarks regarding iteration
Serving #67073 or other developments
@cuviper

Copy link
Copy Markdown
Member

I will try to review it this week, or else yield to another reviewer.

@Mark-Simulacrum

Copy link
Copy Markdown
Member

FWIW I've been trying to get up to speed on the BTreeMap code so definitely feel free to yield to me if needed.

@ssomers

ssomers commented Jan 23, 2020

Copy link
Copy Markdown
ContributorAuthor

Latest commit is just a rebase, no longer showing the benchmarks as proposed changes because they are already merged in.

@cuvipercuviper left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Expose a function to step through trees, without necessarily extracting the KV pair, that helps future operations like drain/retain (as demonstrated in this drain_filter implementation)

I do appreciate consolidating the code that steps through the tree, as that's complicated enough to be annoying to have it repeated in multiple places. I'm not totally comfortable with the abstraction achieved here though. For one, not extracting the KV right away means we have to keep multiple handles alive, which is quietly dangerous in the Mut and Owned cases, as I've noted inline below. I accept that miri is OK with this as used now, but there's a greater risk of introducing unchecked mistakes in the future.

Preserve the way two handles are (temporarily) derived from one handle: implementations for immutable maps (i.e. Range::next_unchecked and Range::next_back_unchecked) rely on copying (implicitly before, explicitly now) and the others do ptr::read.

That "(temporarily)" is quite important though -- we should not sweep this under the rug!

I think the functions that support iterators on immutable trees (i.e. Range::next_unchecked and Range::next_back_unchecked) are erroneously tagged unsafe since you can already create multiple instances of such ranges, thus obtain multiple handles into the same tree. I did not change that but removed unsafe from the functions underneath.

They're still unsafe for being unchecked, to assume that there is in fact a next item. Maybe your navigate functions should be similarly unchecked, removing that Option uncertainty and the "changes nothing" caveat.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's not really correct to say it changes nothing in the deallocate_and_ascend case -- in fact the original handle could be left dangling, no? I suppose that's currently avoided because the callers expect/know there is always another item, since they track their length.

I'm not really comfortable with quietly copying handles in the Mut and Owned cases. You did mark those unsafe, at least, but there's no indication to callers about the actual danger involved. Compare this to the reborrow_mut methods which say, "Beware, as this method is very dangerous, doubly so since it may not immediately appear dangerous."

I understand the original iterators had this effect too with their ptr::read duplication, but that was local, and it really should have been commented as well. You mention possibly passing in the handle by value, where I guess we could keep the responsibility for that copy on the caller's side -- how significant was your observed slowdown? It seems like that shouldn't impact inlined performance at all, just moving which side of the call does the copy.

@ssomersssomersJan 23, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's not really correct to say it changes nothing in the deallocate_and_ascend case

Indeed, that's an awkward case.

I suppose that's currently avoided because the callers expect/know there is always another item, since they track their length.

Yes, and in addition, the current callers (apart from the Drop implementation) are double-ended iterators. They could stop when front and back pointer are equal instead of tracking length, but they cannot use this "end-of-tree" return value. The only code actually using it is my drain_filter implementation, and there it's just a wee bit more elegant than tracking length. I mostly just liked the idea of having a totally safe function on immutable trees.

there's no indication to callers about the actual danger involved.

I'm not really sure what the danger is. To me, it's that these hopping functions return two mutable handles pointing inside the same structure, so once the caller uses either to change something, the other one is on fire.

You mention possibly passing in the handle by value, where I guess we could keep the responsibility for that copy on the caller's side -- how significant was your observed slowdown?

Between 5 and 10%. Normally within the range of noise, but so consistent over various changes back and forth that I guess it's really there. I tried in vain to reproduce under laboratory conditions and asked around, but no real conclusion.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not really sure what the danger is. To me, it's that these hopping functions return two mutable handles pointing inside the same structure, so once the caller uses either to change something, the other one is on fire.

Yes, invalidation like that is obviously bad. A simpler example is just the existence of safe Handle::into_kv_mut -- then separate handles could give you aliasing &mut, which is UB. We can admonish btree developers not to do that, but the point is that we've bypassed the compiler's usual protections against these hazards.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

not extracting the KV right away means we have to keep multiple handles alive, which is quietly dangerous in the Mut and Owned cases

I'm relatively new to Rust and don't grasp much of unsafety. I think I understand why extracting KV right away would be better: to reduce the attack surface of mistakes or abuse.

But the initiative to keep multiple handles alive in the Mut case was #58431, specifically done to make things, well, safer? better? possible? compatible with stacked borrows (just showing off I learnt a new term today). I don't really understand what the problem was, nor why it wasn't a problem in the Owned case, so definitely not how to extract KV right away in the Mut case and avoid #58431.

@cuviper

Copy link
Copy Markdown
Member

@Mark-Simulacrum since you're also interested, maybe you can weigh in on the danger of duplicate handles too. I could be overreacting, but I think that at least needs to be better documented.

@ssomers

ssomers commented Jan 26, 2020

Copy link
Copy Markdown
ContributorAuthor

How about this:

  • 3 simpler macros Instead of 1 with weird support functions
  • The functions dangerously duplicating handles still exist, but they are private in module navigate and wrapped by less unsafe functions.
  • Which means more of the juicy stuff juggling with handles gets moved over to module navigate and fat module map sheds more weight.
  • With more documentation.

Performance comparison with master code:

>cargo benchcmp ol3.txt new3.txt --threshold 5
name ol3.txt ns/iter new3.txt ns/iter diff ns/iter diff % speedup
btree::map::find_rand_100 19 20 1 5.26% x 0.95
btree::map::find_seq_100 19 20 1 5.26% x 0.95
btree::map::first_and_last_0 39 43 4 10.26% x 0.91
btree::map::iter_1000 4,094 4,373 279 6.81% x 0.94
btree::map::iter_100000 482,150 524,020 41,870 8.68% x 0.92
btree::map::iter_20 66 79 13 19.70% x 0.84
btree::map::iter_mut_1000 4,723 4,391 -332 -7.03% x 1.08
btree::set::build_and_clear 5,037 4,537 -500 -9.93% x 1.11
btree::set::build_and_drop 4,904 4,650 -254 -5.18% x 1.05
btree::set::build_and_into_iter 5,025 4,466 -559 -11.12% x 1.13
btree::set::build_and_pop_all 6,749 6,402 -347 -5.14% x 1.05
btree::set::build_and_remove_all 8,368 7,471 -897 -10.72% x 1.12
btree::set::difference_random_100_vs_100 1,558 1,698 140 8.99% x 0.92
btree::set::difference_random_10k_vs_100 79,070 85,602 6,532 8.26% x 0.92
btree::set::difference_random_10k_vs_10k 208,150 224,872 16,722 8.03% x 0.93
btree::set::difference_staggered_100_vs_100 2,298 1,690 -608 -26.46% x 1.36
btree::set::difference_staggered_100_vs_10k 2,506 2,835 329 13.13% x 0.88
btree::set::difference_staggered_10k_vs_10k 222,852 171,746 -51,106 -22.93% x 1.30
btree::set::intersection_100_neg_vs_100_pos 29 32 3 10.34% x 0.91
btree::set::intersection_100_neg_vs_10k_pos 34 36 2 5.88% x 0.94
btree::set::intersection_100_pos_vs_10k_neg 34 36 2 5.88% x 0.94
btree::set::intersection_10k_neg_vs_10k_pos 35 37 2 5.71% x 0.95
btree::set::intersection_10k_pos_vs_10k_neg 35 41 6 17.14% x 0.85
btree::set::intersection_random_100_vs_100 773 840 67 8.67% x 0.92
btree::set::intersection_staggered_100_vs_100 791 897 106 13.40% x 0.88
btree::set::intersection_staggered_100_vs_10k 2,275 2,497 222 9.76% x 0.91
btree::set::intersection_staggered_10k_vs_10k 76,342 86,109 9,767 12.79% x 0.89
btree::set::is_subset_100_vs_100 628 768 140 22.29% x 0.82
btree::set::is_subset_10k_vs_10k 63,406 79,173 15,767 24.87% x 0.80

As you see, 5 to 10% reported loss in ordinary iteration speed, which can be cured by replacing the by value interface with references (which looks way less ugly in the Immut case, because copies are implicit). But a similar reported boost in mutable iteration, which doesn't make much sense because the code is the same, just chopped up in functions.

@ssomersssomers changed the title Expose and align 6 BTreeMap navigation algorithmsBundle and document 6 BTreeMap navigation algorithmsJan 27, 2020
@ssomers

Copy link
Copy Markdown
ContributorAuthor

I forgot the rest of the changes I wanted to do, i.e. revert to the order of handle manipulation that is in master (which is easy once there were 3 macros). So that this PR is purely about reorganizing code and exposing functions for later use (i.e. drain_filter). Same points as above, but the performance now is on par with master:

>cargo benchcmp ol2.txt newer3.txt --threshold 5
name ol2.txt ns/iter newer3.txt ns/iter diff ns/iter diff % speedup
btree::map::insert_seq_10_000 98 107 9 9.18% x 0.92
btree::map::iter_1000 4,129 3,818 -311 -7.53% x 1.08
btree::map::iter_mut_1000 4,647 4,361 -286 -6.15% x 1.07
btree::set::build_and_remove_all 8,705 7,991 -714 -8.20% x 1.09
btree::set::intersection_random_100_vs_100 783 737 -46 -5.87% x 1.06
btree::set::intersection_random_10k_vs_10k 169,476 151,990 -17,486 -10.32% x 1.12
btree::set::intersection_staggered_100_vs_100 792 845 53 6.69% x 0.94
btree::set::intersection_staggered_100_vs_10k 2,308 2,533 225 9.75% x 0.91
btree::set::is_subset_100_vs_100 628 680 52 8.28% x 0.92

It turns out that delaying the into_kv calls speeds up benchmarks for Owned access, and slows them down for Immut access. Changing by-value to by-reference semantics doesn't make any difference anymore; maybe because the macro-generated functions are wrapped by functions with reference semantics already, maybe because of changes in rustc.

@ssomers

Copy link
Copy Markdown
ContributorAuthor

on the danger of duplicate handles

One danger I struggled with, is on a higher level of duplication: in RangeMut. This is much less temporary and contained then what the guts of mutable iterators did and do. I tried to explain in the precondition of hop_to_next_unchecked_deallocating how these two handle variables have to keep out of each other's hair.

@cuvipercuviper left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like this a lot better! There's a clearer value now in having these all defined in the same focused module, and the required safety is much better explained.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(bikeshed) These names are quite a mouthful. What if we just made them all inherent methods on Handle? They can even overlap names since their borrow-type markers are different. Something like:

impl<'a,K,V>Handle<NodeRef<marker::Immut<'a>,K,V, marker::Leaf>, marker::Edge>{pubunsafefnnext_unchecked(&mutself) -> (&'aK,&'aV){ ...}pubunsafefnnext_back_unchecked(&mutself) -> (&'aK,&'aV){ ...}}impl<'a,K,V>Handle<NodeRef<marker::Mut<'a>,K,V, marker::Leaf>, marker::Edge>{pubunsafefnnext_unchecked(&mutself) -> (&'aK,&'amutV){ ...}pubunsafefnnext_back_unchecked(&mutself) -> (&'aK,&'amutV){ ...}}impl<K,V>Handle<NodeRef<marker::Owned,K,V, marker::Leaf>, marker::Edge>{pubunsafefnnext_unchecked(&mutself) -> (K,V){ ...}pubunsafefnnext_back_unchecked(&mutself) -> (K,V){ ...}}

I think that will improve readability at the call sites too:

- let (k, v) =- unsafe { navigate::hop_to_next_back_unchecked_deallocating(&mut self.back) };- Some((k, v))+ Some(unsafe { self.back.next_back_unchecked() })

@ssomersssomersJan 30, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I am happy either way, I don't know what's the usual or desired style is, but are you aware that first_leaf_edge/last_leaf_edge were standalone functions in map.rs, and that all impl blocks in map.rs are for types defined in itself? So I figured that in order to make for instance first_leaf_edge a method on NodeRef, like first_edge is, it would have to move to node.rs. Which would be both good (there is some similarity) and bad (node.rs is pretty fat already, though not as fat as map.rs). search.rs seemed most similar, and it has standalone functions.

Secondly, is it obvious that iterating over an owned tree deallocates it? I guess since it returns ownership of K and V, you have to realize the tree can't survive and the function has to be used in an IntoIterator. But if you're the owner of a tree and you somehow want to iterate through it (sending everyone a letter of resignation) without already destroying it?

Thirdly, if we do that, then do we remove the paper thin, private Range::next_back_unchecked function?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Also, I stuck on the hop_on_ prefix specifically to distinguish them from the existing next_... functions. But since they're prefixed with navigate:: when used, that was pointless.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Naming is certainly subjective, which is why I prefixed this as a bikeshed comment. 🙂

it would have to move to node.rs.

It does not have to move -- methods can be added from anywhere in the same crate. Maybe the original author of first/last_leaf_edge just didn't know you could do that. Such methods even get privacy according to where they are defined, rather than from the type definition.

These are all pub anyway, though not fully publicly reachable -- maybe they should be tightened to pub(crate) or even pub(super). (That's a possible cleanup across most of btree.)

Secondly, is it obvious that iterating over an owned tree deallocates it?

Seems ok to me, but that's a subjective point too.

Thirdly, if we do that, then do we remove the paper thin, private Range::next_back_unchecked function?

There's still a little value in discriminating the range's front or back for those that are calling self.inner.next_unchecked etc.

Also, I stuck on the hop_on_ prefix specifically to distinguish them from the existing next_... functions. But since they're prefixed with navigate:: when used, that was pointless.

Well, there aren't existing next* methods on Handle, at least, so I think it's ok. It's a nice symmetry IMO that outer next just calls the handle's next_unchecked, and next_back calls next_back_unchecked.

@ssomersssomersJan 30, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

maybe they should be tightened to pub(crate) or even pub(super).

Aren't they already effectively something like that because mod.rs does not pub-lish module node?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Aren't they already effectively something like that because mod.rs does not pub-lish module node?

Right, they're not actually reachable, and the compiler knows that -- it will even tell you that with RUSTFLAGS="-W unreachable_pub" or #![warn(unreachable_pub)] in the code. That lint is allowed (silent) by default.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Note that while rebasing, I noticed #66648 has added a mut to the return signature of next_unchecked. I'm doing the same in next_back_unchecked.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changing the pub visibility would be more about making sure they're never exposed by accident. But the compiler enforces stability attributes on truly public items in the standard library anyway, due to #![feature(staged_api)], so I guess that doesn't matter.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Also, added the same is_empty to Range as was added to RangeMut and narrowed down the unsafe sections.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

These are all pub anyway, though not fully publicly reachable

Not sure what you mean with "not fully", but if they're not explicitly pub in navigate.rs, trying to call them in map.rs makes the compiler bark violently that they're private. So I'd rather say they're fully not public.

@bors

bors commented Jan 30, 2020

Copy link
Copy Markdown
Collaborator

☔ The latest upstream changes (presumably #68659) made this pull request unmergeable. Please resolve the merge conflicts.

@ssomers

ssomers commented Jan 30, 2020

Copy link
Copy Markdown
ContributorAuthor

Miri is still happy, full test build with debug-assertions passes.

@ssomers

Copy link
Copy Markdown
ContributorAuthor

I missed that the comments in navigate.rs had to adapt to their handle method ecosystem.

@cuvipercuviper left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks great!

@cuviper

Copy link
Copy Markdown
Member

@bors r+

@bors

bors commented Jan 31, 2020

Copy link
Copy Markdown
Collaborator

📌 Commit 3cf724d has been approved by cuviper

@borsbors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 31, 2020
@bors

bors commented Jan 31, 2020

Copy link
Copy Markdown
Collaborator

⌛ Testing commit 3cf724d with merge cd1ef39...

bors added a commit that referenced this pull request Jan 31, 2020
Bundle and document 6 BTreeMap navigation algorithms
- Expose a function to step through trees, without necessarily extracting the KV pair, that helps future operations like drain/retain (as demonstrated in [this drain_filter implementation](https://github.com/ssomers/rust/compare/btree_navigation_v3...ssomers:btree_drain_filter?expand=1))
- ~~Also aligns the implementation of the 2 x 3 iterators already using such navigation:~~
- ~~Delay the moment the K,V references are plucked from the tree, for the 4 iterators on immutable and owned maps, just for symmetry. The same had to be done to the two iterators for mutable maps in #58431.~~
- ~~Always explicitly use ptr::read to derive two handles from one handle. While the existing implementations for immutable maps (i.e. Range::next_unchecked and Range::next_back_unchecked) rely on implicit copying. There's no change in unsafe tags because these two functions were already (erroneously? prophetically?) tagged unsafe. I don't know whether they should be tagged unsafe. I guess they should be for mutable and owned maps, because you can change the map through one handle and leave the other handle invalid.~~
- Preserve the way two handles are (temporarily) derived from one handle: implementations for immutable maps (i.e. Range::next_unchecked and Range::next_back_unchecked) rely on copying (implicitly before, explicitly now) and the others do `ptr::read`.
- ~~I think the functions that support iterators on immutable trees (i.e. `Range::next_unchecked` and `Range::next_back_unchecked`) are erroneously tagged unsafe since you can already create multiple instances of such ranges, thus obtain multiple handles into the same tree. I did not change that but removed unsafe from the functions underneath.~~
Tested with miri in liballoc/tests/btree, except those that should_panic.
cargo benchcmp the best of 3 samples of all btree benchmarks before and after this PR:
```
name old1.txt ns/iter new2.txt ns/iter diff ns/iter diff % speedup
btree::map::find_rand_100 17 17 0 0.00% x 1.00
btree::map::find_rand_10_000 57 55 -2 -3.51% x 1.04
btree::map::find_seq_100 17 17 0 0.00% x 1.00
btree::map::find_seq_10_000 42 39 -3 -7.14% x 1.08
btree::map::first_and_last_0 14 14 0 0.00% x 1.00
btree::map::first_and_last_100 36 37 1 2.78% x 0.97
btree::map::first_and_last_10k 52 52 0 0.00% x 1.00
btree::map::insert_rand_100 34 34 0 0.00% x 1.00
btree::map::insert_rand_10_000 34 34 0 0.00% x 1.00
btree::map::insert_seq_100 46 46 0 0.00% x 1.00
btree::map::insert_seq_10_000 90 89 -1 -1.11% x 1.01
btree::map::iter_1000 2,811 2,771 -40 -1.42% x 1.01
btree::map::iter_100000 360,635 355,995 -4,640 -1.29% x 1.01
btree::map::iter_20 39 42 3 7.69% x 0.93
btree::map::iter_mut_1000 2,662 2,864 202 7.59% x 0.93
btree::map::iter_mut_100000 336,825 346,550 9,725 2.89% x 0.97
btree::map::iter_mut_20 40 43 3 7.50% x 0.93
btree::set::build_and_clear 4,184 3,994 -190 -4.54% x 1.05
btree::set::build_and_drop 4,151 3,976 -175 -4.22% x 1.04
btree::set::build_and_into_iter 4,196 4,155 -41 -0.98% x 1.01
btree::set::build_and_pop_all 5,176 5,241 65 1.26% x 0.99
btree::set::build_and_remove_all 6,868 6,903 35 0.51% x 0.99
btree::set::difference_random_100_vs_100 721 691 -30 -4.16% x 1.04
btree::set::difference_random_100_vs_10k 2,420 2,411 -9 -0.37% x 1.00
btree::set::difference_random_10k_vs_100 54,726 52,215 -2,511 -4.59% x 1.05
btree::set::difference_random_10k_vs_10k 164,384 170,256 5,872 3.57% x 0.97
btree::set::difference_staggered_100_vs_100 739 709 -30 -4.06% x 1.04
btree::set::difference_staggered_100_vs_10k 2,320 2,265 -55 -2.37% x 1.02
btree::set::difference_staggered_10k_vs_10k 68,020 70,246 2,226 3.27% x 0.97
btree::set::intersection_100_neg_vs_100_pos 23 24 1 4.35% x 0.96
btree::set::intersection_100_neg_vs_10k_pos 28 29 1 3.57% x 0.97
btree::set::intersection_100_pos_vs_100_neg 24 24 0 0.00% x 1.00
btree::set::intersection_100_pos_vs_10k_neg 28 28 0 0.00% x 1.00
btree::set::intersection_10k_neg_vs_100_pos 27 27 0 0.00% x 1.00
btree::set::intersection_10k_neg_vs_10k_pos 30 29 -1 -3.33% x 1.03
btree::set::intersection_10k_pos_vs_100_neg 27 28 1 3.70% x 0.96
btree::set::intersection_10k_pos_vs_10k_neg 29 29 0 0.00% x 1.00
btree::set::intersection_random_100_vs_100 592 572 -20 -3.38% x 1.03
btree::set::intersection_random_100_vs_10k 2,271 2,269 -2 -0.09% x 1.00
btree::set::intersection_random_10k_vs_100 2,301 2,333 32 1.39% x 0.99
btree::set::intersection_random_10k_vs_10k 147,879 150,148 2,269 1.53% x 0.98
btree::set::intersection_staggered_100_vs_100 622 632 10 1.61% x 0.98
btree::set::intersection_staggered_100_vs_10k 2,101 2,032 -69 -3.28% x 1.03
btree::set::intersection_staggered_10k_vs_10k 60,341 61,834 1,493 2.47% x 0.98
btree::set::is_subset_100_vs_100 417 426 9 2.16% x 0.98
btree::set::is_subset_100_vs_10k 1,281 1,324 43 3.36% x 0.97
btree::set::is_subset_10k_vs_100 2 2 0 0.00% x 1.00
btree::set::is_subset_10k_vs_10k 41,054 41,612 558 1.36% x 0.99
```
r? cuviper
@bors

bors commented Jan 31, 2020

Copy link
Copy Markdown
Collaborator

☀️ Test successful - checks-azure
Approved by: cuviper
Pushing cd1ef39 to master...

@borsbors added the merged-by-bors This PR was explicitly merged by bors. label Jan 31, 2020
@bors
bors merged commit 3cf724d into rust-lang:masterJan 31, 2020
@ssomers
ssomers deleted the btree_navigation branch January 31, 2020 23:03
bors added a commit that referenced this pull request Feb 28, 2020
…ulacrum
BTreeMap navigation done safer & faster
It turns out that there was a faster way to do the tree navigation code bundled in #67073, by moving from edge to KV and from KV to next edge separately. It extracts most of the code as safe functions, and contains the duplication of handles within the short wrapper functions.
This somehow hits a sweet spot in the compiler because it reports boosts all over the board:
```
>cargo benchcmp pre3.txt posz4.txt --threshold 5
name pre3.txt ns/iter posz4.txt ns/iter diff ns/iter diff % speedup
btree::map::first_and_last_0 40 37 -3 -7.50% x 1.08
btree::map::first_and_last_100 58 44 -14 -24.14% x 1.32
btree::map::iter_1000 8,920 3,419 -5,501 -61.67% x 2.61
btree::map::iter_100000 1,069,290 411,615 -657,675 -61.51% x 2.60
btree::map::iter_20 169 58 -111 -65.68% x 2.91
btree::map::iter_mut_1000 8,701 3,303 -5,398 -62.04% x 2.63
btree::map::iter_mut_100000 1,034,560 405,975 -628,585 -60.76% x 2.55
btree::map::iter_mut_20 165 58 -107 -64.85% x 2.84
btree::set::clone_100 1,831 1,562 -269 -14.69% x 1.17
btree::set::clone_100_and_clear 1,831 1,565 -266 -14.53% x 1.17
btree::set::clone_100_and_into_iter 1,917 1,541 -376 -19.61% x 1.24
btree::set::clone_100_and_pop_all 2,609 2,441 -168 -6.44% x 1.07
btree::set::clone_100_and_remove_all 4,598 3,927 -671 -14.59% x 1.17
btree::set::clone_100_and_remove_half 2,765 2,551 -214 -7.74% x 1.08
btree::set::clone_10k 191,610 164,616 -26,994 -14.09% x 1.16
btree::set::clone_10k_and_clear 192,003 164,616 -27,387 -14.26% x 1.17
btree::set::clone_10k_and_into_iter 200,037 163,010 -37,027 -18.51% x 1.23
btree::set::clone_10k_and_pop_all 267,023 250,913 -16,110 -6.03% x 1.06
btree::set::clone_10k_and_remove_all 536,230 464,100 -72,130 -13.45% x 1.16
btree::set::clone_10k_and_remove_half 453,350 430,545 -22,805 -5.03% x 1.05
btree::set::difference_random_100_vs_100 1,787 801 -986 -55.18% x 2.23
btree::set::difference_random_100_vs_10k 2,978 2,696 -282 -9.47% x 1.10
btree::set::difference_random_10k_vs_100 111,075 54,734 -56,341 -50.72% x 2.03
btree::set::difference_random_10k_vs_10k 246,380 175,980 -70,400 -28.57% x 1.40
btree::set::difference_staggered_100_vs_100 1,789 951 -838 -46.84% x 1.88
btree::set::difference_staggered_100_vs_10k 2,798 2,606 -192 -6.86% x 1.07
btree::set::difference_staggered_10k_vs_10k 176,452 97,401 -79,051 -44.80% x 1.81
btree::set::intersection_100_neg_vs_10k_pos 34 32 -2 -5.88% x 1.06
btree::set::intersection_100_pos_vs_100_neg 30 27 -3 -10.00% x 1.11
btree::set::intersection_random_100_vs_100 1,537 613 -924 -60.12% x 2.51
btree::set::intersection_random_100_vs_10k 2,793 2,649 -144 -5.16% x 1.05
btree::set::intersection_random_10k_vs_10k 222,127 147,166 -74,961 -33.75% x 1.51
btree::set::intersection_staggered_100_vs_100 1,447 622 -825 -57.01% x 2.33
btree::set::intersection_staggered_100_vs_10k 2,606 2,382 -224 -8.60% x 1.09
btree::set::intersection_staggered_10k_vs_10k 143,620 58,790 -84,830 -59.07% x 2.44
btree::set::is_subset_100_vs_100 1,349 488 -861 -63.83% x 2.76
btree::set::is_subset_100_vs_10k 1,720 1,428 -292 -16.98% x 1.20
btree::set::is_subset_10k_vs_10k 135,984 48,527 -87,457 -64.31% x 2.80
```
The `first_and_last` ones are noise (they don't do iteration), the others seem genuine.
As always, approved by Miri.
Also, a separate commit with some more benchmarks of mutable behaviour (which also benefit).
r? @cuviper
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged-by-borsThis PR was explicitly merged by bors.S-waiting-on-borsStatus: Waiting on bors to run and complete tests. Bors will change the label on completion.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@ssomers@rust-highfive@cuviper@bors@Mark-Simulacrum@kennytm