Auto merge of #2865 - Vanille-N:tb-perf, r=RalfJung

Thorough merge after GC: fix of #2863

Context: #2863.

`perf report`s of `MIRIFLAGS=-Zmiri-tree-borrows cargo +miri miri test test_invalid_name_lengths` in crate `http`:

### Pre
```
  91.06%  rustc    miri                                 [.] miri::borrow_tracker::tree_borrows::tree::Tree::keep_only_needed
   2.99%  rustc    miri                                 [.] miri::borrow_tracker::tree_borrows::tree::TreeVisitor::traverse_parents_this
   0.91%  rustc    miri                                 [.] miri::borrow_tracker::tree_borrows::tree::Tree::perform_access
   0.62%  rustc    miri                                 [.] miri::range_map::RangeMap<T>::iter_mut
   0.17%  rustc    libc.so.6                            [.] realloc
   0.14%  rustc    miri                                 [.] miri::concurrency:🧵:EvalContextExt::run_threads
   0.13%  rustc    miri                                 [.] rustc_const_eval::interpret::operand::<impl rustc_const_eval::interpret::eva
   0.13%  rustc    miri                                 [.] hashbrown::raw::RawTable<T,A>::remove_entry
   0.10%  rustc    miri                                 [.] miri::intptrcast::GlobalStateInner::alloc_base_addr
   0.08%  rustc    librustc_driver-c82c1dc22c817a10.so  [.] <rustc_middle::mir::Body>::source_info
```
Interrupted after 3min 30s.

### Post
```
  20.75%  rustc    miri                                 [.] miri::borrow_tracker::tree_borrows::tree::TreeVisitor::traverse_parents_this
  18.50%  rustc    miri                                 [.] miri::borrow_tracker::tree_borrows::tree::Tree::keep_only_needed
   6.49%  rustc    miri                                 [.] miri::borrow_tracker::tree_borrows::tree::Tree::perform_access
   4.25%  rustc    miri                                 [.] miri::range_map::RangeMap<T>::iter_mut
   1.91%  rustc    libc.so.6                            [.] realloc
   1.79%  rustc    miri                                 [.] miri::concurrency:🧵:EvalContextExt::run_threads
   1.40%  rustc    miri                                 [.] rustc_const_eval::interpret::operand::<impl rustc_const_eval::interpret::eva
   1.40%  rustc    miri                                 [.] miri::range_map::RangeMap<T>::merge_adjacent_thorough
   1.34%  rustc    miri                                 [.] miri::intptrcast::GlobalStateInner::alloc_base_addr
   0.90%  rustc    librustc_driver-c82c1dc22c817a10.so  [.] <rustc_middle::ty::context::CtxtInterners>::intern_ty
```
Terminates after 1min 13s.

No significant changes to `./miri bench` in either direction: on small benches not enough garbage accumulates for this to be relevant.
This commit is contained in:
bors 2023-05-10 06:30:26 +00:00
commit 7fb4332ce4
6 changed files with 92 additions and 2 deletions

View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "zip-equal"
version = "0.1.0"

View File

@ -0,0 +1,8 @@
[package]
name = "zip-equal"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -0,0 +1,22 @@
//! This is a pathological pattern in which opportunities to merge
//! adjacent identical items in the RangeMap are not properly detected
//! because `RangeMap::iter_mut` is never called on overlapping ranges
//! and thus never merges previously split ranges. This does not produce any
//! additional cost for access operations, but it makes the job of the Tree Borrows
//! GC procedure much more costly.
//! See https://github.com/rust-lang/miri/issues/2863
const LENGTH: usize = (1 << 14) - 1;
const LONG: &[u8] = &[b'x'; LENGTH];
fn main() {
assert!(eq(LONG, LONG))
}
fn eq(s1: &[u8], s2: &[u8]) -> bool {
if s1.len() != s2.len() {
return false;
}
s1.iter().zip(s2).all(|(c1, c2)| *c1 == *c2)
}

View File

@ -488,7 +488,13 @@ impl<'tcx> Tree {
/// Integration with the BorTag garbage collector /// Integration with the BorTag garbage collector
impl Tree { impl Tree {
pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) { pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) {
assert!(self.keep_only_needed(self.root, live_tags)); // root can't be removed let root_is_needed = self.keep_only_needed(self.root, live_tags); // root can't be removed
assert!(root_is_needed);
// Right after the GC runs is a good moment to check if we can
// merge some adjacent ranges that were made equal by the removal of some
// tags (this does not necessarily mean that they have identical internal representations,
// see the `PartialEq` impl for `UniValMap`)
self.rperms.merge_adjacent_thorough();
} }
/// Traverses the entire tree looking for useless tags. /// Traverses the entire tree looking for useless tags.

View File

@ -36,13 +36,42 @@ pub struct UniKeyMap<K> {
} }
/// From UniIndex to V /// From UniIndex to V
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, Eq)]
pub struct UniValMap<V> { pub struct UniValMap<V> {
/// The mapping data. Thanks to Vec we get both fast accesses, and /// The mapping data. Thanks to Vec we get both fast accesses, and
/// a memory-optimal representation if there are few deletions. /// a memory-optimal representation if there are few deletions.
data: Vec<Option<V>>, data: Vec<Option<V>>,
} }
impl<V: PartialEq> UniValMap<V> {
/// Exact equality of two maps.
/// Less accurate but faster than `equivalent`, mostly because
/// of the fast path when the lengths are different.
pub fn identical(&self, other: &Self) -> bool {
self.data == other.data
}
/// Equality up to trailing `None`s of two maps, i.e.
/// do they represent the same mapping ?
pub fn equivalent(&self, other: &Self) -> bool {
let min_len = self.data.len().min(other.data.len());
self.data[min_len..].iter().all(Option::is_none)
&& other.data[min_len..].iter().all(Option::is_none)
&& (self.data[..min_len] == other.data[..min_len])
}
}
impl<V: PartialEq> PartialEq for UniValMap<V> {
/// 2023-05: We found that using `equivalent` rather than `identical`
/// in the equality testing of the `RangeMap` is neutral for most
/// benchmarks, while being quite beneficial for `zip-equal`
/// and to a lesser extent for `unicode`, `slice-get-unchecked` and
/// `backtraces` as well.
fn eq(&self, other: &Self) -> bool {
self.equivalent(other)
}
}
impl<V> Default for UniValMap<V> { impl<V> Default for UniValMap<V> {
fn default() -> Self { fn default() -> Self {
Self { data: Vec::default() } Self { data: Vec::default() }

View File

@ -227,6 +227,24 @@ impl<T> RangeMap<T> {
}; };
slice.iter_mut().map(|elem| (elem.range.clone(), &mut elem.data)) slice.iter_mut().map(|elem| (elem.range.clone(), &mut elem.data))
} }
/// Remove all adjacent duplicates
pub fn merge_adjacent_thorough(&mut self)
where
T: PartialEq,
{
let clean = Vec::with_capacity(self.v.len());
for elem in std::mem::replace(&mut self.v, clean) {
if let Some(prev) = self.v.last_mut() {
if prev.data == elem.data {
assert_eq!(prev.range.end, elem.range.start);
prev.range.end = elem.range.end;
continue;
}
}
self.v.push(elem);
}
}
} }
#[cfg(test)] #[cfg(test)]