-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #50240 - nnethercote:LazyBTreeMap, r=cramertj
Implement LazyBTreeMap and use it in a few places. This is a thin wrapper around BTreeMap that avoids allocating upon creation. I would prefer to change BTreeMap directly to make it lazy (like I did with HashSet in #36734) and I initially attempted that by making BTreeMap::root an Option<>. But then I also had to change Iter and Range to handle trees with no root, and those types have stability markers on them and I wasn't sure if that was acceptable. Also, BTreeMap has a lot of complex code and changing it all was challenging, and I didn't have high confidence about my general approach. So I prototyped this wrapper instead and used it in the hottest locations to get some measurements about the effect. The measurements are pretty good! - Doing a debug build of serde, it reduces the total number of heap allocations from 17,728,709 to 13,359,384, a 25% reduction. The number of bytes allocated drops from 7,474,672,966 to 5,482,308,388, a 27% reduction. - It gives speedups of up to 3.6% on some rustc-perf benchmark jobs. crates.io, futures, and serde benefit most. ``` futures-check avg: -1.9% min: -3.6% max: -0.5% serde-check avg: -2.1% min: -3.5% max: -0.7% crates.io-check avg: -1.7% min: -3.5% max: -0.3% serde avg: -2.0% min: -3.0% max: -0.9% serde-opt avg: -1.8% min: -2.9% max: -0.3% futures avg: -1.5% min: -2.8% max: -0.4% tokio-webpush-simple-check avg: -1.1% min: -2.2% max: -0.1% futures-opt avg: -1.2% min: -2.1% max: -0.4% piston-image-check avg: -0.8% min: -1.1% max: -0.3% crates.io avg: -0.6% min: -1.0% max: -0.3% ``` @gankro, how do you think I should proceed here? Is leaving this as a wrapper reasonable? Or should I try to make BTreeMap itself lazy? If so, can I change the representation of Iter and Range? Thanks!
- Loading branch information
Showing
5 changed files
with
123 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
use std::collections::btree_map; | ||
use std::collections::BTreeMap; | ||
|
||
/// A thin wrapper around BTreeMap that avoids allocating upon creation. | ||
/// | ||
/// Vec, HashSet and HashMap all have the nice feature that they don't do any | ||
/// heap allocation when creating a new structure of the default size. In | ||
/// contrast, BTreeMap *does* allocate in that situation. The compiler uses | ||
/// B-Tree maps in some places such that many maps are created but few are | ||
/// inserted into, so having a BTreeMap alternative that avoids allocating on | ||
/// creation is a performance win. | ||
/// | ||
/// Only a fraction of BTreeMap's functionality is currently supported. | ||
/// Additional functionality should be added on demand. | ||
#[derive(Debug)] | ||
pub struct LazyBTreeMap<K, V>(Option<BTreeMap<K, V>>); | ||
|
||
impl<K, V> LazyBTreeMap<K, V> { | ||
pub fn new() -> LazyBTreeMap<K, V> { | ||
LazyBTreeMap(None) | ||
} | ||
|
||
pub fn iter(&self) -> Iter<K, V> { | ||
Iter(self.0.as_ref().map(|btm| btm.iter())) | ||
} | ||
|
||
pub fn is_empty(&self) -> bool { | ||
self.0.as_ref().map_or(true, |btm| btm.is_empty()) | ||
} | ||
} | ||
|
||
impl<K: Ord, V> LazyBTreeMap<K, V> { | ||
fn instantiate(&mut self) -> &mut BTreeMap<K, V> { | ||
if let Some(ref mut btm) = self.0 { | ||
btm | ||
} else { | ||
let btm = BTreeMap::new(); | ||
self.0 = Some(btm); | ||
self.0.as_mut().unwrap() | ||
} | ||
} | ||
|
||
pub fn insert(&mut self, key: K, value: V) -> Option<V> { | ||
self.instantiate().insert(key, value) | ||
} | ||
|
||
pub fn entry(&mut self, key: K) -> btree_map::Entry<K, V> { | ||
self.instantiate().entry(key) | ||
} | ||
|
||
pub fn values<'a>(&'a self) -> Values<'a, K, V> { | ||
Values(self.0.as_ref().map(|btm| btm.values())) | ||
} | ||
} | ||
|
||
impl<K: Ord, V> Default for LazyBTreeMap<K, V> { | ||
fn default() -> LazyBTreeMap<K, V> { | ||
LazyBTreeMap::new() | ||
} | ||
} | ||
|
||
impl<'a, K: 'a, V: 'a> IntoIterator for &'a LazyBTreeMap<K, V> { | ||
type Item = (&'a K, &'a V); | ||
type IntoIter = Iter<'a, K, V>; | ||
|
||
fn into_iter(self) -> Iter<'a, K, V> { | ||
self.iter() | ||
} | ||
} | ||
|
||
pub struct Iter<'a, K: 'a, V: 'a>(Option<btree_map::Iter<'a, K, V>>); | ||
|
||
impl<'a, K: 'a, V: 'a> Iterator for Iter<'a, K, V> { | ||
type Item = (&'a K, &'a V); | ||
|
||
fn next(&mut self) -> Option<(&'a K, &'a V)> { | ||
self.0.as_mut().and_then(|iter| iter.next()) | ||
} | ||
|
||
fn size_hint(&self) -> (usize, Option<usize>) { | ||
self.0.as_ref().map_or_else(|| (0, Some(0)), |iter| iter.size_hint()) | ||
} | ||
} | ||
|
||
pub struct Values<'a, K: 'a, V: 'a>(Option<btree_map::Values<'a, K, V>>); | ||
|
||
impl<'a, K, V> Iterator for Values<'a, K, V> { | ||
type Item = &'a V; | ||
|
||
fn next(&mut self) -> Option<&'a V> { | ||
self.0.as_mut().and_then(|values| values.next()) | ||
} | ||
|
||
fn size_hint(&self) -> (usize, Option<usize>) { | ||
self.0.as_ref().map_or_else(|| (0, Some(0)), |values| values.size_hint()) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters