Skip to content

Commit 636412c

Browse files
committed
Optimize slice::binary_search_by
Except for ZSTs, `[T].len()` is less than `isize::MAX`. By treating the ZST case separately, we can "revert" 3eb5bee, remove the `size` local variable and win two instructions.
1 parent 4bc39f0 commit 636412c

File tree

2 files changed

+32
-13
lines changed

2 files changed

+32
-13
lines changed

library/core/src/slice/mod.rs

+30-11
Original file line numberDiff line numberDiff line change
@@ -2787,37 +2787,56 @@ impl<T> [T] {
27872787
where
27882788
F: FnMut(&'a T) -> Ordering,
27892789
{
2790+
// If T is a ZST, we assume that f is a constant function.
2791+
// We do so because:
2792+
// 1. ZST can only have one inhabitant
2793+
// 2. We assume that f doesn't compare the address of the reference
2794+
// passed, but only the value pointed to.
2795+
// 3. We assume f's output to be entirely determined by its input
2796+
if T::IS_ZST {
2797+
let res = if self.len() == 0 {
2798+
Err(0)
2799+
} else {
2800+
match f(&self[0]) {
2801+
Less => Err(self.len()),
2802+
Equal => Ok(0),
2803+
Greater => Err(0),
2804+
}
2805+
};
2806+
return res;
2807+
}
2808+
// Now we can assume that T is not a ZST, so self.len() <= isize::MAX
27902809
// INVARIANTS:
2791-
// - 0 <= left <= left + size = right <= self.len()
2810+
// - 0 <= left <= right <= self.len() <= isize::MAX
27922811
// - f returns Less for everything in self[..left]
27932812
// - f returns Greater for everything in self[right..]
2794-
let mut size = self.len();
2813+
let mut right = self.len();
27952814
let mut left = 0;
2796-
let mut right = size;
27972815
while left < right {
2798-
let mid = left + size / 2;
2816+
// left + right <= 2*isize::MAX < usize::MAX
2817+
// so the addition won't overflow
2818+
let mid = (left + right) / 2;
27992819

2800-
// SAFETY: the while condition means `size` is strictly positive, so
2801-
// `size/2 < size`. Thus `left + size/2 < left + size`, which
2802-
// coupled with the `left + size <= self.len()` invariant means
2803-
// we have `left + size/2 < self.len()`, and this is in-bounds.
2820+
// SAFETY: We have that left < right, so
2821+
// 0 <= left <= mid < right <= self.len()
2822+
// and the indexing is in-bounds.
28042823
let cmp = f(unsafe { self.get_unchecked(mid) });
28052824

28062825
// This control flow produces conditional moves, which results in
28072826
// fewer branches and instructions than if/else or matching on
28082827
// cmp::Ordering.
28092828
// This is x86 asm for u8: https://rust.godbolt.org/z/698eYffTx.
2829+
// (Note: the code as slightly changed since this comment but the
2830+
// reasoning remains the same.)
2831+
28102832
left = if cmp == Less { mid + 1 } else { left };
28112833
right = if cmp == Greater { mid } else { right };
28122834
if cmp == Equal {
28132835
// SAFETY: same as the `get_unchecked` above
28142836
unsafe { hint::assert_unchecked(mid < self.len()) };
28152837
return Ok(mid);
28162838
}
2817-
2818-
size = right - left;
28192839
}
2820-
28212840
// SAFETY: directly true from the overall invariant.
28222841
// Note that this is `<=`, unlike the assume in the `Ok` path.
28232842
unsafe { hint::assert_unchecked(left <= self.len()) };

library/core/tests/slice.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ fn test_binary_search() {
6969
assert_eq!(b.binary_search(&8), Err(5));
7070

7171
let b = [(); usize::MAX];
72-
assert_eq!(b.binary_search(&()), Ok(usize::MAX / 2));
72+
assert_eq!(b.binary_search(&()), Ok(0));
7373
}
7474

7575
#[test]
7676
fn test_binary_search_by_overflow() {
7777
let b = [(); usize::MAX];
78-
assert_eq!(b.binary_search_by(|_| Ordering::Equal), Ok(usize::MAX / 2));
78+
assert_eq!(b.binary_search_by(|_| Ordering::Equal), Ok(0));
7979
assert_eq!(b.binary_search_by(|_| Ordering::Greater), Err(0));
8080
assert_eq!(b.binary_search_by(|_| Ordering::Less), Err(usize::MAX));
8181
}

0 commit comments

Comments
 (0)