Skip to content

Commit

Permalink
Add intrusive iterators to BTree.
Browse files Browse the repository at this point in the history
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?
  • Loading branch information
Clark Gaebel committed Dec 13, 2014
1 parent ffc1118 commit def348d
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 1 deletion.
93 changes: 93 additions & 0 deletions src/libcollections/btree/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,7 @@ impl<K, V, E, T: Traverse<E> + DoubleEndedIterator<TraversalItem<K, V, E>>>
// 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
Expand Down Expand Up @@ -1030,6 +1031,7 @@ impl<K, V, E, T: Traverse<E> + DoubleEndedIterator<TraversalItem<K, V, E>>>
}

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<uint>) { self.inner.size_hint() }
}
Expand All @@ -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<uint>) { 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<K, V> Iterator<(K, V)> for MoveEntries<K, V> {
#[inline]
fn next(&mut self) -> Option<(K, V)> { self.inner.next() }
fn size_hint(&self) -> (uint, Option<uint>) { self.inner.size_hint() }
}
impl<K, V> DoubleEndedIterator<(K, V)> for MoveEntries<K, V> {
#[inline]
fn next_back(&mut self) -> Option<(K, V)> { self.inner.next_back() }
}
impl<K, V> ExactSizeIterator<(K, V)> for MoveEntries<K, V> {}
Expand Down Expand Up @@ -1130,6 +1136,25 @@ impl<K, V> BTreeMap<K, V> {
}
}

/// 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<F: FnMut(&K, &V)>(&self, mut f: F) {
fn intrusive_iter_impl<K, V, F: FnMut(&K, &V)>(node: &Node<K, V>, 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> {
Expand All @@ -1144,6 +1169,25 @@ impl<K, V> BTreeMap<K, V> {
}
}

/// 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<F: FnMut(&K, &mut V)>(&mut self, mut f: F) {
fn intrusive_iter_mut_impl<K, V, F: FnMut(&K, &mut V)>(node: &mut Node<K, V>, 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<K, V> {
Expand All @@ -1158,6 +1202,25 @@ impl<K, V> BTreeMap<K, V> {
}
}

/// 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<F: FnMut(K, V)>(self, mut f: F) {
fn intrusive_into_iter_impl<K, V, F: FnMut(K, V)>(node: Node<K, V>, 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
Expand Down Expand Up @@ -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::<uint, uint>::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);
}
}
10 changes: 10 additions & 0 deletions src/libcollections/btree/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1334,10 +1334,14 @@ struct ElemsAndEdges<Elems, Edges>(Elems, Edges);
impl<K, V, E, Elems: DoubleEndedIterator<(K, V)>, Edges: DoubleEndedIterator<E>>
TraversalImpl<K, V, E> for ElemsAndEdges<Elems, Edges> {

#[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<E> { self.1.next() }
#[inline]
fn next_edge_back(&mut self) -> Option<E> { self.1.next_back() }
}

Expand All @@ -1354,26 +1358,30 @@ struct MoveTraversalImpl<K, V> {
}

impl<K, V> TraversalImpl<K, V, Node<K, V>> for MoveTraversalImpl<K, V> {
#[inline]
fn next_kv(&mut self) -> Option<(K, V)> {
match (self.keys.next(), self.vals.next()) {
(Some(k), Some(v)) => Some((k, v)),
_ => None
}
}

#[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)),
_ => None
}
}

#[inline]
fn next_edge(&mut self) -> Option<Node<K, V>> {
// Necessary for correctness, but in a private module
debug_assert!(!self.is_leaf);
self.edges.next()
}

#[inline]
fn next_edge_back(&mut self) -> Option<Node<K, V>> {
// Necessary for correctness, but in a private module
debug_assert!(!self.is_leaf);
Expand Down Expand Up @@ -1427,6 +1435,7 @@ pub type MoveTraversal<K, V> = AbsTraversal<MoveTraversalImpl<K, V>>;
impl<K, V, E, Impl: TraversalImpl<K, V, E>>
Iterator<TraversalItem<K, V, E>> for AbsTraversal<Impl> {

#[inline]
fn next(&mut self) -> Option<TraversalItem<K, V, E>> {
let head_is_edge = self.head_is_edge;
self.head_is_edge = !head_is_edge;
Expand All @@ -1442,6 +1451,7 @@ impl<K, V, E, Impl: TraversalImpl<K, V, E>>
impl<K, V, E, Impl: TraversalImpl<K, V, E>>
DoubleEndedIterator<TraversalItem<K, V, E>> for AbsTraversal<Impl> {

#[inline]
fn next_back(&mut self) -> Option<TraversalItem<K, V, E>> {
let tail_is_edge = self.tail_is_edge;
self.tail_is_edge = !tail_is_edge;
Expand Down
2 changes: 1 addition & 1 deletion src/libcollections/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit def348d

Please sign in to comment.