Skip to content

Commit bcfd3f7

Browse files
committed
Auto merge of rust-lang#86898 - the8472:path-cmp, r=dtolnay
Add fast path for Path::cmp that skips over long shared prefixes ``` # before test path::tests::bench_path_cmp_fast_path_buf_sort ... bench: 60,811 ns/iter (+/- 865) test path::tests::bench_path_cmp_fast_path_long ... bench: 6,459 ns/iter (+/- 275) test path::tests::bench_path_cmp_fast_path_short ... bench: 1,777 ns/iter (+/- 34) # after test path::tests::bench_path_cmp_fast_path_buf_sort ... bench: 38,140 ns/iter (+/- 211) test path::tests::bench_path_cmp_fast_path_long ... bench: 1,471 ns/iter (+/- 24) test path::tests::bench_path_cmp_fast_path_short ... bench: 1,106 ns/iter (+/- 9) ```
2 parents 7611fe4 + dfdf361 commit bcfd3f7

File tree

2 files changed

+108
-7
lines changed

2 files changed

+108
-7
lines changed

library/std/src/path.rs

+40-7
Original file line numberDiff line numberDiff line change
@@ -962,16 +962,49 @@ impl cmp::Eq for Components<'_> {}
962962
impl<'a> cmp::PartialOrd for Components<'a> {
963963
#[inline]
964964
fn partial_cmp(&self, other: &Components<'a>) -> Option<cmp::Ordering> {
965-
Iterator::partial_cmp(self.clone(), other.clone())
965+
Some(compare_components(self.clone(), other.clone()))
966966
}
967967
}
968968

969969
#[stable(feature = "rust1", since = "1.0.0")]
970970
impl cmp::Ord for Components<'_> {
971971
#[inline]
972972
fn cmp(&self, other: &Self) -> cmp::Ordering {
973-
Iterator::cmp(self.clone(), other.clone())
973+
compare_components(self.clone(), other.clone())
974+
}
975+
}
976+
977+
fn compare_components(mut left: Components<'_>, mut right: Components<'_>) -> cmp::Ordering {
978+
// Fast path for long shared prefixes
979+
//
980+
// - compare raw bytes to find first mismatch
981+
// - backtrack to find separator before mismatch to avoid ambiguous parsings of '.' or '..' characters
982+
// - if found update state to only do a component-wise comparison on the remainder,
983+
// otherwise do it on the full path
984+
//
985+
// The fast path isn't taken for paths with a PrefixComponent to avoid backtracking into
986+
// the middle of one
987+
if left.prefix.is_none() && right.prefix.is_none() && left.front == right.front {
988+
// this might benefit from a [u8]::first_mismatch simd implementation, if it existed
989+
let first_difference =
990+
match left.path.iter().zip(right.path.iter()).position(|(&a, &b)| a != b) {
991+
None if left.path.len() == right.path.len() => return cmp::Ordering::Equal,
992+
None => left.path.len().min(right.path.len()),
993+
Some(diff) => diff,
994+
};
995+
996+
if let Some(previous_sep) =
997+
left.path[..first_difference].iter().rposition(|&b| left.is_sep_byte(b))
998+
{
999+
let mismatched_component_start = previous_sep + 1;
1000+
left.path = &left.path[mismatched_component_start..];
1001+
left.front = State::Body;
1002+
right.path = &right.path[mismatched_component_start..];
1003+
right.front = State::Body;
1004+
}
9741005
}
1006+
1007+
Iterator::cmp(left, right)
9751008
}
9761009

9771010
/// An iterator over [`Path`] and its ancestors.
@@ -1704,15 +1737,15 @@ impl cmp::Eq for PathBuf {}
17041737
impl cmp::PartialOrd for PathBuf {
17051738
#[inline]
17061739
fn partial_cmp(&self, other: &PathBuf) -> Option<cmp::Ordering> {
1707-
self.components().partial_cmp(other.components())
1740+
Some(compare_components(self.components(), other.components()))
17081741
}
17091742
}
17101743

17111744
#[stable(feature = "rust1", since = "1.0.0")]
17121745
impl cmp::Ord for PathBuf {
17131746
#[inline]
17141747
fn cmp(&self, other: &PathBuf) -> cmp::Ordering {
1715-
self.components().cmp(other.components())
1748+
compare_components(self.components(), other.components())
17161749
}
17171750
}
17181751

@@ -2686,7 +2719,7 @@ impl fmt::Display for Display<'_> {
26862719
impl cmp::PartialEq for Path {
26872720
#[inline]
26882721
fn eq(&self, other: &Path) -> bool {
2689-
self.components().eq(other.components())
2722+
self.components() == other.components()
26902723
}
26912724
}
26922725

@@ -2706,15 +2739,15 @@ impl cmp::Eq for Path {}
27062739
impl cmp::PartialOrd for Path {
27072740
#[inline]
27082741
fn partial_cmp(&self, other: &Path) -> Option<cmp::Ordering> {
2709-
self.components().partial_cmp(other.components())
2742+
Some(compare_components(self.components(), other.components()))
27102743
}
27112744
}
27122745

27132746
#[stable(feature = "rust1", since = "1.0.0")]
27142747
impl cmp::Ord for Path {
27152748
#[inline]
27162749
fn cmp(&self, other: &Path) -> cmp::Ordering {
2717-
self.components().cmp(other.components())
2750+
compare_components(self.components(), other.components())
27182751
}
27192752
}
27202753

library/std/src/path/tests.rs

+68
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use super::*;
22

3+
use crate::collections::BTreeSet;
34
use crate::rc::Rc;
45
use crate::sync::Arc;
6+
use core::hint::black_box;
57

68
macro_rules! t(
79
($path:expr, iter: $iter:expr) => (
@@ -1392,3 +1394,69 @@ fn into_rc() {
13921394
assert_eq!(&*rc2, path);
13931395
assert_eq!(&*arc2, path);
13941396
}
1397+
1398+
#[test]
1399+
fn test_ord() {
1400+
macro_rules! ord(
1401+
($ord:ident, $left:expr, $right:expr) => ( {
1402+
assert_eq!(Path::new($left).cmp(&Path::new($right)), core::cmp::Ordering::$ord);
1403+
});
1404+
);
1405+
1406+
ord!(Less, "1", "2");
1407+
ord!(Less, "/foo/bar", "/foo./bar");
1408+
ord!(Less, "foo/bar", "foo/bar.");
1409+
ord!(Equal, "foo/./bar", "foo/bar/");
1410+
ord!(Equal, "foo/bar", "foo/bar/");
1411+
ord!(Equal, "foo/bar", "foo/bar/.");
1412+
ord!(Equal, "foo/bar", "foo/bar//");
1413+
}
1414+
1415+
#[bench]
1416+
fn bench_path_cmp_fast_path_buf_sort(b: &mut test::Bencher) {
1417+
let prefix = "my/home";
1418+
let mut paths: Vec<_> =
1419+
(0..1000).map(|num| PathBuf::from(prefix).join(format!("file {}.rs", num))).collect();
1420+
1421+
paths.sort();
1422+
1423+
b.iter(|| {
1424+
black_box(paths.as_mut_slice()).sort_unstable();
1425+
});
1426+
}
1427+
1428+
#[bench]
1429+
fn bench_path_cmp_fast_path_long(b: &mut test::Bencher) {
1430+
let prefix = "/my/home/is/my/castle/and/my/castle/has/a/rusty/workbench/";
1431+
let paths: Vec<_> =
1432+
(0..1000).map(|num| PathBuf::from(prefix).join(format!("file {}.rs", num))).collect();
1433+
1434+
let mut set = BTreeSet::new();
1435+
1436+
paths.iter().for_each(|p| {
1437+
set.insert(p.as_path());
1438+
});
1439+
1440+
b.iter(|| {
1441+
set.remove(paths[500].as_path());
1442+
set.insert(paths[500].as_path());
1443+
});
1444+
}
1445+
1446+
#[bench]
1447+
fn bench_path_cmp_fast_path_short(b: &mut test::Bencher) {
1448+
let prefix = "my/home";
1449+
let paths: Vec<_> =
1450+
(0..1000).map(|num| PathBuf::from(prefix).join(format!("file {}.rs", num))).collect();
1451+
1452+
let mut set = BTreeSet::new();
1453+
1454+
paths.iter().for_each(|p| {
1455+
set.insert(p.as_path());
1456+
});
1457+
1458+
b.iter(|| {
1459+
set.remove(paths[500].as_path());
1460+
set.insert(paths[500].as_path());
1461+
});
1462+
}

0 commit comments

Comments
 (0)