From def348d4cca5e37b486fa44fb78a0227dea8c9f0 Mon Sep 17 00:00:00 2001 From: Clark Gaebel Date: Fri, 12 Dec 2014 17:11:52 -0800 Subject: [PATCH] Add intrusive iterators to BTree. Unfortunately, tree structures are intrinsically slower to iterate over externally than internally. This can be demonstrated in benchmarks. In fact, it's so bad at external iteration that calling `.find` on each element in succession is currently slightly faster. This patch implements a faster intrusive way to iterate over BTrees. This is about 5x faster, but you lose all iterator composition infrastructure. This is a tradeoff that is acceptable in some applications. Relevant benchmarks: ``` test btree::map::bench::intrusive_iter_1000 ... bench: 2658 ns/iter (+/- 602) test btree::map::bench::intrusive_iter_100000 ... bench: 346353 ns/iter (+/- 189565) test btree::map::bench::intrusive_iter_20 ... bench: 55 ns/iter (+/- 16) test btree::map::bench::iter_1000 ... bench: 15892 ns/iter (+/- 3717) test btree::map::bench::iter_100000 ... bench: 1383714 ns/iter (+/- 444706) test btree::map::bench::iter_20 ... bench: 366 ns/iter (+/- 104) ``` r? @Gankro @huonw @aturon how does this fit into 1.0 stabilization plans. Is marking this as #[experimental] enough? --- src/libcollections/btree/map.rs | 93 ++++++++++++++++++++++++++++++++ src/libcollections/btree/node.rs | 10 ++++ src/libcollections/lib.rs | 2 +- 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/src/libcollections/btree/map.rs b/src/libcollections/btree/map.rs index f0a4902bb9235..8fa535b6ea4b4 100644 --- a/src/libcollections/btree/map.rs +++ b/src/libcollections/btree/map.rs @@ -937,6 +937,7 @@ impl + DoubleEndedIterator>> // making these arbitrary sub-range iterators. However the logic to construct these paths // efficiently is fairly involved, so this is a FIXME. The sub-range iterators also wouldn't be // able to accurately predict size, so those iterators can't implement ExactSizeIterator. + #[inline] fn next(&mut self) -> Option<(K, V)> { loop { // We want the smallest element, so try to get the top of the left stack @@ -1030,6 +1031,7 @@ impl + DoubleEndedIterator>> } impl<'a, K, V> Iterator<(&'a K, &'a V)> for Entries<'a, K, V> { + #[inline] fn next(&mut self) -> Option<(&'a K, &'a V)> { self.inner.next() } fn size_hint(&self) -> (uint, Option) { self.inner.size_hint() } } @@ -1040,20 +1042,24 @@ impl<'a, K, V> ExactSizeIterator<(&'a K, &'a V)> for Entries<'a, K, V> {} impl<'a, K, V> Iterator<(&'a K, &'a mut V)> for MutEntries<'a, K, V> { + #[inline] fn next(&mut self) -> Option<(&'a K, &'a mut V)> { self.inner.next() } fn size_hint(&self) -> (uint, Option) { self.inner.size_hint() } } impl<'a, K, V> DoubleEndedIterator<(&'a K, &'a mut V)> for MutEntries<'a, K, V> { + #[inline] fn next_back(&mut self) -> Option<(&'a K, &'a mut V)> { self.inner.next_back() } } impl<'a, K, V> ExactSizeIterator<(&'a K, &'a mut V)> for MutEntries<'a, K, V> {} impl Iterator<(K, V)> for MoveEntries { + #[inline] fn next(&mut self) -> Option<(K, V)> { self.inner.next() } fn size_hint(&self) -> (uint, Option) { self.inner.size_hint() } } impl DoubleEndedIterator<(K, V)> for MoveEntries { + #[inline] fn next_back(&mut self) -> Option<(K, V)> { self.inner.next_back() } } impl ExactSizeIterator<(K, V)> for MoveEntries {} @@ -1130,6 +1136,25 @@ impl BTreeMap { } } + /// An intrusive version of `.iter()`. The closure will be called once with + /// every key/value pair in the tree. + /// + /// This is faster than calling `.iter()`, but is far less composable. + #[inline] + #[experimental = "relies on unboxed closures"] + pub fn intrusive_iter(&self, mut f: F) { + fn intrusive_iter_impl(node: &Node, f: &mut F) { + for ti in node.iter() { + match ti { + Elem(k, v) => (*f)(k, v), + Edge(e) => intrusive_iter_impl(e, f), + } + } + } + + intrusive_iter_impl(&self.root, &mut f); + } + /// Gets a mutable iterator over the entries of the map. #[unstable = "matches collection reform specification, waiting for dust to settle"] pub fn iter_mut<'a>(&'a mut self) -> MutEntries<'a, K, V> { @@ -1144,6 +1169,25 @@ impl BTreeMap { } } + /// An intrusive version of `.iter_mut()`. The closure will be called once + /// with every key/value pair in the tree. + /// + /// This is faster than calling `.iter_mut()`, but is far less composable. + #[inline] + #[experimental = "relies on unboxed closures"] + pub fn intrusive_iter_mut(&mut self, mut f: F) { + fn intrusive_iter_mut_impl(node: &mut Node, f: &mut F) { + for ti in node.iter_mut() { + match ti { + Elem(k, v) => (*f)(k, v), + Edge(e) => intrusive_iter_mut_impl(e, f), + } + } + } + + intrusive_iter_mut_impl(&mut self.root, &mut f); + } + /// Gets an owning iterator over the entries of the map. #[unstable = "matches collection reform specification, waiting for dust to settle"] pub fn into_iter(self) -> MoveEntries { @@ -1158,6 +1202,25 @@ impl BTreeMap { } } + /// An intrusive version of `.into_iter()`. The closure will be called once + /// with every key/value pair in the tree. + /// + /// This is faster than calling `.into_iter()`, but is far less composable. + #[inline] + #[experimental = "relies on unboxed closures"] + pub fn intrusive_into_iter(self, mut f: F) { + fn intrusive_into_iter_impl(node: Node, f: &mut F) { + for ti in node.into_iter() { + match ti { + Elem(k, v) => (*f)(k, v), + Edge(e) => intrusive_into_iter_impl(e, f), + } + } + } + + intrusive_into_iter_impl(self.root, &mut f); + } + /// Gets an iterator over the keys of the map. /// /// # Examples @@ -1580,4 +1643,34 @@ mod bench { pub fn iter_100000(b: &mut Bencher) { bench_iter(b, 100000); } + + fn bench_intrusive_iter(b: &mut Bencher, size: uint) { + let mut map = BTreeMap::::new(); + let mut rng = weak_rng(); + + for _ in range(0, size) { + map.insert(rng.gen(), rng.gen()); + } + + b.iter(|| { + map.intrusive_iter(|&mut: k, v| { + black_box(k); black_box(v); + }); + }); + } + + #[bench] + pub fn intrusive_iter_20(b: &mut Bencher) { + bench_intrusive_iter(b, 20); + } + + #[bench] + pub fn intrusive_iter_1000(b: &mut Bencher) { + bench_intrusive_iter(b, 1000); + } + + #[bench] + pub fn intrusive_iter_100000(b: &mut Bencher) { + bench_intrusive_iter(b, 100000); + } } diff --git a/src/libcollections/btree/node.rs b/src/libcollections/btree/node.rs index ae23f38c92913..28c87388d53c1 100644 --- a/src/libcollections/btree/node.rs +++ b/src/libcollections/btree/node.rs @@ -1334,10 +1334,14 @@ struct ElemsAndEdges(Elems, Edges); impl, Edges: DoubleEndedIterator> TraversalImpl for ElemsAndEdges { + #[inline] fn next_kv(&mut self) -> Option<(K, V)> { self.0.next() } + #[inline] fn next_kv_back(&mut self) -> Option<(K, V)> { self.0.next_back() } + #[inline] fn next_edge(&mut self) -> Option { self.1.next() } + #[inline] fn next_edge_back(&mut self) -> Option { self.1.next_back() } } @@ -1354,6 +1358,7 @@ struct MoveTraversalImpl { } impl TraversalImpl> for MoveTraversalImpl { + #[inline] fn next_kv(&mut self) -> Option<(K, V)> { match (self.keys.next(), self.vals.next()) { (Some(k), Some(v)) => Some((k, v)), @@ -1361,6 +1366,7 @@ impl TraversalImpl> for MoveTraversalImpl { } } + #[inline] fn next_kv_back(&mut self) -> Option<(K, V)> { match (self.keys.next_back(), self.vals.next_back()) { (Some(k), Some(v)) => Some((k, v)), @@ -1368,12 +1374,14 @@ impl TraversalImpl> for MoveTraversalImpl { } } + #[inline] fn next_edge(&mut self) -> Option> { // Necessary for correctness, but in a private module debug_assert!(!self.is_leaf); self.edges.next() } + #[inline] fn next_edge_back(&mut self) -> Option> { // Necessary for correctness, but in a private module debug_assert!(!self.is_leaf); @@ -1427,6 +1435,7 @@ pub type MoveTraversal = AbsTraversal>; impl> Iterator> for AbsTraversal { + #[inline] fn next(&mut self) -> Option> { let head_is_edge = self.head_is_edge; self.head_is_edge = !head_is_edge; @@ -1442,6 +1451,7 @@ impl> impl> DoubleEndedIterator> for AbsTraversal { + #[inline] fn next_back(&mut self) -> Option> { let tail_is_edge = self.tail_is_edge; self.tail_is_edge = !tail_is_edge; diff --git a/src/libcollections/lib.rs b/src/libcollections/lib.rs index 039bbcd2b7020..274fa13d07449 100644 --- a/src/libcollections/lib.rs +++ b/src/libcollections/lib.rs @@ -24,7 +24,7 @@ #![allow(unknown_features)] #![feature(macro_rules, default_type_params, phase, globs)] #![feature(unsafe_destructor, import_shadowing, slicing_syntax)] -#![feature(tuple_indexing, unboxed_closures)] +#![feature(unboxed_closures)] #![no_std] #[phase(plugin, link)] extern crate core;