Skip to content

Commit 18e44a1

Browse files
committed
Improve slice.binary_search_by()'s best-case performance to O(1)
1 parent 0248c6f commit 18e44a1

File tree

3 files changed

+47
-11
lines changed

3 files changed

+47
-11
lines changed

library/core/benches/slice.rs

+38-6
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@ enum Cache {
77
L3,
88
}
99

10+
impl Cache {
11+
fn size(&self) -> usize {
12+
match self {
13+
Cache::L1 => 1000, // 8kb
14+
Cache::L2 => 10_000, // 80kb
15+
Cache::L3 => 1_000_000, // 8Mb
16+
}
17+
}
18+
}
19+
1020
fn binary_search<F>(b: &mut Bencher, cache: Cache, mapper: F)
1121
where
1222
F: Fn(usize) -> usize,
1323
{
14-
let size = match cache {
15-
Cache::L1 => 1000, // 8kb
16-
Cache::L2 => 10_000, // 80kb
17-
Cache::L3 => 1_000_000, // 8Mb
18-
};
24+
let size = cache.size();
1925
let v = (0..size).map(&mapper).collect::<Vec<_>>();
2026
let mut r = 0usize;
2127
b.iter(move || {
@@ -24,7 +30,18 @@ where
2430
// Lookup the whole range to get 50% hits and 50% misses.
2531
let i = mapper(r % size);
2632
black_box(v.binary_search(&i).is_ok());
27-
})
33+
});
34+
}
35+
36+
fn binary_search_worst_case(b: &mut Bencher, cache: Cache) {
37+
let size = cache.size();
38+
39+
let mut v = vec![0; size];
40+
let i = 1;
41+
v[size - 1] = i;
42+
b.iter(move || {
43+
black_box(v.binary_search(&i).is_ok());
44+
});
2845
}
2946

3047
#[bench]
@@ -57,6 +74,21 @@ fn binary_search_l3_with_dups(b: &mut Bencher) {
5774
binary_search(b, Cache::L3, |i| i / 16 * 16);
5875
}
5976

77+
#[bench]
78+
fn binary_search_l1_worst_case(b: &mut Bencher) {
79+
binary_search_worst_case(b, Cache::L1);
80+
}
81+
82+
#[bench]
83+
fn binary_search_l2_worst_case(b: &mut Bencher) {
84+
binary_search_worst_case(b, Cache::L2);
85+
}
86+
87+
#[bench]
88+
fn binary_search_l3_worst_case(b: &mut Bencher) {
89+
binary_search_worst_case(b, Cache::L3);
90+
}
91+
6092
macro_rules! rotate {
6193
($fn:ident, $n:expr, $mapper:expr) => {
6294
#[bench]

library/core/src/slice/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -2167,7 +2167,11 @@ impl<T> [T] {
21672167
// - `mid >= 0`: by definition
21682168
// - `mid < size`: `mid = size / 2 + size / 4 + size / 8 ...`
21692169
let cmp = f(unsafe { s.get_unchecked(mid) });
2170-
base = if cmp == Greater { base } else { mid };
2170+
if cmp == Equal {
2171+
return Ok(mid);
2172+
} else if cmp == Less {
2173+
base = mid
2174+
}
21712175
size -= half;
21722176
}
21732177
// SAFETY: base is always in [0, size) because base <= mid.

library/core/tests/slice.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@ fn test_binary_search_implementation_details() {
7373
let b = [1, 1, 2, 2, 3, 3, 3];
7474
assert_eq!(b.binary_search(&1), Ok(1));
7575
assert_eq!(b.binary_search(&2), Ok(3));
76-
assert_eq!(b.binary_search(&3), Ok(6));
76+
assert_eq!(b.binary_search(&3), Ok(5));
7777
let b = [1, 1, 1, 1, 1, 3, 3, 3, 3];
7878
assert_eq!(b.binary_search(&1), Ok(4));
79-
assert_eq!(b.binary_search(&3), Ok(8));
79+
assert_eq!(b.binary_search(&3), Ok(6));
8080
let b = [1, 1, 1, 1, 3, 3, 3, 3, 3];
81-
assert_eq!(b.binary_search(&1), Ok(3));
82-
assert_eq!(b.binary_search(&3), Ok(8));
81+
assert_eq!(b.binary_search(&1), Ok(2));
82+
assert_eq!(b.binary_search(&3), Ok(4));
8383
}
8484

8585
#[test]

0 commit comments

Comments
 (0)