Use std::borrow::Cow to avoid some memory allocations and copying.
Details
Details
Diff Detail
Diff Detail
- Repository
- rHG Mercurial
- Branch
- default
- Lint
No Linters Available - Unit
No Unit Test Coverage
( )
Use std::borrow::Cow to avoid some memory allocations and copying.
No Linters Available |
No Unit Test Coverage |
Path | Packages | |||
---|---|---|---|---|
M | rust/hg-core/src/dirstate_tree/dirstate_map.rs (31 lines) | |||
M | rust/hg-core/src/dirstate_tree/path_with_basename.rs (15 lines) |
Commit | Parents | Author | Summary | Date |
---|---|---|---|---|
52dae3d27aa2 | bc70e5406e73 | Simon Sapin | Apr 30 2021, 2:21 PM |
Status | Author | Revision | |
---|---|---|---|
Abandoned | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin |
} | } | ||||
/// Using a plain `HgPathBuf` of the full path from the repository root as a | /// Using a plain `HgPathBuf` of the full path from the repository root as a | ||||
/// map key would also work: all paths in a given map have the same parent | /// map key would also work: all paths in a given map have the same parent | ||||
/// path, so comparing full paths gives the same result as comparing base | /// path, so comparing full paths gives the same result as comparing base | ||||
/// names. However `BTreeMap` would waste time always re-comparing the same | /// names. However `BTreeMap` would waste time always re-comparing the same | ||||
/// string prefix. | /// string prefix. | ||||
pub(super) type ChildNodes<'on_disk> = | pub(super) type ChildNodes<'on_disk> = | ||||
FastHashMap<WithBasename<HgPathBuf>, Node<'on_disk>>; | FastHashMap<WithBasename<Cow<'on_disk, HgPath>>, Node<'on_disk>>; | ||||
/// Represents a file or a directory | /// Represents a file or a directory | ||||
#[derive(Default)] | #[derive(Default)] | ||||
pub(super) struct Node<'on_disk> { | pub(super) struct Node<'on_disk> { | ||||
/// `None` for directories | /// `None` for directories | ||||
pub(super) entry: Option<DirstateEntry>, | pub(super) entry: Option<DirstateEntry>, | ||||
pub(super) copy_source: Option<Cow<'on_disk, HgPath>>, | pub(super) copy_source: Option<Cow<'on_disk, HgPath>>, | ||||
if self.on_disk.is_empty() { | if self.on_disk.is_empty() { | ||||
return Ok(None); | return Ok(None); | ||||
} | } | ||||
let parents = parse_dirstate_entries( | let parents = parse_dirstate_entries( | ||||
self.on_disk, | self.on_disk, | ||||
|path, entry, copy_source| { | |path, entry, copy_source| { | ||||
let tracked = entry.state.is_tracked(); | let tracked = entry.state.is_tracked(); | ||||
let node = Self::get_or_insert_node_tracing_ancestors( | let node = Self::get_or_insert_node( | ||||
&mut self.root, | &mut self.root, | ||||
path, | path, | ||||
WithBasename::to_cow_borrowed, | |||||
|ancestor| { | |ancestor| { | ||||
if tracked { | if tracked { | ||||
ancestor.tracked_descendants_count += 1 | ancestor.tracked_descendants_count += 1 | ||||
} | } | ||||
}, | }, | ||||
); | ); | ||||
assert!( | assert!( | ||||
node.entry.is_none(), | node.entry.is_none(), | ||||
component = next_component; | component = next_component; | ||||
children = &mut child.children; | children = &mut child.children; | ||||
} else { | } else { | ||||
return Some(child); | return Some(child); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
fn get_or_insert_node<'tree>( | fn get_or_insert_node<'tree, 'path>( | ||||
root: &'tree mut ChildNodes<'on_disk>, | root: &'tree mut ChildNodes<'on_disk>, | ||||
path: &HgPath, | path: &'path HgPath, | ||||
) -> &'tree mut Node<'on_disk> { | to_cow: impl Fn( | ||||
Self::get_or_insert_node_tracing_ancestors(root, path, |_| {}) | WithBasename<&'path HgPath>, | ||||
} | ) -> WithBasename<Cow<'on_disk, HgPath>>, | ||||
fn get_or_insert_node_tracing_ancestors<'tree>( | |||||
root: &'tree mut ChildNodes<'on_disk>, | |||||
path: &HgPath, | |||||
mut each_ancestor: impl FnMut(&mut Node), | mut each_ancestor: impl FnMut(&mut Node), | ||||
) -> &'tree mut Node<'on_disk> { | ) -> &'tree mut Node<'on_disk> { | ||||
let mut child_nodes = root; | let mut child_nodes = root; | ||||
let mut inclusive_ancestor_paths = | let mut inclusive_ancestor_paths = | ||||
WithBasename::inclusive_ancestors_of(path); | WithBasename::inclusive_ancestors_of(path); | ||||
let mut ancestor_path = inclusive_ancestor_paths | let mut ancestor_path = inclusive_ancestor_paths | ||||
.next() | .next() | ||||
.expect("expected at least one inclusive ancestor"); | .expect("expected at least one inclusive ancestor"); | ||||
loop { | loop { | ||||
// TODO: can we avoid allocating an owned key in cases where the | // TODO: can we avoid allocating an owned key in cases where the | ||||
// map already contains that key, without introducing double | // map already contains that key, without introducing double | ||||
// lookup? | // lookup? | ||||
let child_node = | let child_node = | ||||
child_nodes.entry(ancestor_path.to_owned()).or_default(); | child_nodes.entry(to_cow(ancestor_path)).or_default(); | ||||
if let Some(next) = inclusive_ancestor_paths.next() { | if let Some(next) = inclusive_ancestor_paths.next() { | ||||
each_ancestor(child_node); | each_ancestor(child_node); | ||||
ancestor_path = next; | ancestor_path = next; | ||||
child_nodes = &mut child_node.children; | child_nodes = &mut child_node.children; | ||||
} else { | } else { | ||||
return child_node; | return child_node; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
fn add_or_remove_file( | fn add_or_remove_file( | ||||
&mut self, | &mut self, | ||||
path: &HgPath, | path: &HgPath, | ||||
old_state: EntryState, | old_state: EntryState, | ||||
new_entry: DirstateEntry, | new_entry: DirstateEntry, | ||||
) { | ) { | ||||
let tracked_count_increment = | let tracked_count_increment = | ||||
match (old_state.is_tracked(), new_entry.state.is_tracked()) { | match (old_state.is_tracked(), new_entry.state.is_tracked()) { | ||||
(false, true) => 1, | (false, true) => 1, | ||||
(true, false) => -1, | (true, false) => -1, | ||||
_ => 0, | _ => 0, | ||||
}; | }; | ||||
let node = Self::get_or_insert_node_tracing_ancestors( | let node = Self::get_or_insert_node( | ||||
&mut self.root, | &mut self.root, | ||||
path, | path, | ||||
WithBasename::to_cow_owned, | |||||
|ancestor| { | |ancestor| { | ||||
// We can’t use `+= increment` because the counter is unsigned, | // We can’t use `+= increment` because the counter is unsigned, | ||||
// and we want debug builds to detect accidental underflow | // and we want debug builds to detect accidental underflow | ||||
// through zero | // through zero | ||||
match tracked_count_increment { | match tracked_count_increment { | ||||
1 => ancestor.tracked_descendants_count += 1, | 1 => ancestor.tracked_descendants_count += 1, | ||||
-1 => ancestor.tracked_descendants_count -= 1, | -1 => ancestor.tracked_descendants_count -= 1, | ||||
_ => {} | _ => {} | ||||
}) | }) | ||||
} | } | ||||
fn copy_map_insert( | fn copy_map_insert( | ||||
&mut self, | &mut self, | ||||
key: HgPathBuf, | key: HgPathBuf, | ||||
value: HgPathBuf, | value: HgPathBuf, | ||||
) -> Option<HgPathBuf> { | ) -> Option<HgPathBuf> { | ||||
let node = Self::get_or_insert_node(&mut self.root, &key); | let node = Self::get_or_insert_node( | ||||
&mut self.root, | |||||
&key, | |||||
WithBasename::to_cow_owned, | |||||
|_ancestor| {}, | |||||
); | |||||
if node.copy_source.is_none() { | if node.copy_source.is_none() { | ||||
self.nodes_with_copy_source_count += 1 | self.nodes_with_copy_source_count += 1 | ||||
} | } | ||||
node.copy_source.replace(value.into()).map(Cow::into_owned) | node.copy_source.replace(value.into()).map(Cow::into_owned) | ||||
} | } | ||||
fn len(&self) -> usize { | fn len(&self) -> usize { | ||||
self.nodes_with_entry_count | self.nodes_with_entry_count |
use crate::utils::hg_path::HgPath; | use crate::utils::hg_path::HgPath; | ||||
use std::borrow::Borrow; | use std::borrow::{Borrow, Cow}; | ||||
/// Wraps `HgPath` or `HgPathBuf` to make it behave "as" its last path | /// Wraps `HgPath` or `HgPathBuf` to make it behave "as" its last path | ||||
/// component, a.k.a. its base name (as in Python’s `os.path.basename`), but | /// component, a.k.a. its base name (as in Python’s `os.path.basename`), but | ||||
/// also allow recovering the full path. | /// also allow recovering the full path. | ||||
/// | /// | ||||
/// "Behaving as" means that equality and comparison consider only the base | /// "Behaving as" means that equality and comparison consider only the base | ||||
/// name, and `std::borrow::Borrow` is implemented to return only the base | /// name, and `std::borrow::Borrow` is implemented to return only the base | ||||
/// name. This allows using the base name as a map key while still being able | /// name. This allows using the base name as a map key while still being able | ||||
} | } | ||||
impl<T: AsRef<HgPath> + Ord> Ord for WithBasename<T> { | impl<T: AsRef<HgPath> + Ord> Ord for WithBasename<T> { | ||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering { | fn cmp(&self, other: &Self) -> std::cmp::Ordering { | ||||
self.base_name().cmp(other.base_name()) | self.base_name().cmp(other.base_name()) | ||||
} | } | ||||
} | } | ||||
impl<T: ?Sized + ToOwned> WithBasename<&'_ T> { | impl<'a> WithBasename<&'a HgPath> { | ||||
pub fn to_owned(&self) -> WithBasename<T::Owned> { | pub fn to_cow_borrowed(self) -> WithBasename<Cow<'a, HgPath>> { | ||||
WithBasename { | |||||
full_path: Cow::Borrowed(self.full_path), | |||||
base_name_start: self.base_name_start, | |||||
} | |||||
} | |||||
pub fn to_cow_owned<'b>(self) -> WithBasename<Cow<'b, HgPath>> { | |||||
WithBasename { | WithBasename { | ||||
full_path: self.full_path.to_owned(), | full_path: Cow::Owned(self.full_path.to_owned()), | ||||
base_name_start: self.base_name_start, | base_name_start: self.base_name_start, | ||||
} | } | ||||
} | } | ||||
} | } | ||||
impl<'a> WithBasename<&'a HgPath> { | impl<'a> WithBasename<&'a HgPath> { | ||||
/// Returns an iterator of `WithBasename<&HgPath>` for the ancestor | /// Returns an iterator of `WithBasename<&HgPath>` for the ancestor | ||||
/// directory paths of the given `path`, as well as `path` itself. | /// directory paths of the given `path`, as well as `path` itself. |