Skip to content

Commit

Permalink
Improve slice.binary_search_by()'s best-case performance to O(1)
Browse files Browse the repository at this point in the history
  • Loading branch information
Folyd committed Jan 30, 2021
1 parent 0248c6f commit 18e44a1
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 11 deletions.
44 changes: 38 additions & 6 deletions library/core/benches/slice.rs
Expand Up @@ -7,15 +7,21 @@ enum Cache {
L3,
}

impl Cache {
fn size(&self) -> usize {
match self {
Cache::L1 => 1000, // 8kb
Cache::L2 => 10_000, // 80kb
Cache::L3 => 1_000_000, // 8Mb
}
}
}

fn binary_search<F>(b: &mut Bencher, cache: Cache, mapper: F)
where
F: Fn(usize) -> usize,
{
let size = match cache {
Cache::L1 => 1000, // 8kb
Cache::L2 => 10_000, // 80kb
Cache::L3 => 1_000_000, // 8Mb
};
let size = cache.size();
let v = (0..size).map(&mapper).collect::<Vec<_>>();
let mut r = 0usize;
b.iter(move || {
Expand All @@ -24,7 +30,18 @@ where
// Lookup the whole range to get 50% hits and 50% misses.
let i = mapper(r % size);
black_box(v.binary_search(&i).is_ok());
})
});
}

fn binary_search_worst_case(b: &mut Bencher, cache: Cache) {
let size = cache.size();

let mut v = vec![0; size];
let i = 1;
v[size - 1] = i;
b.iter(move || {
black_box(v.binary_search(&i).is_ok());
});
}

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

#[bench]
fn binary_search_l1_worst_case(b: &mut Bencher) {
binary_search_worst_case(b, Cache::L1);
}

#[bench]
fn binary_search_l2_worst_case(b: &mut Bencher) {
binary_search_worst_case(b, Cache::L2);
}

#[bench]
fn binary_search_l3_worst_case(b: &mut Bencher) {
binary_search_worst_case(b, Cache::L3);
}

macro_rules! rotate {
($fn:ident, $n:expr, $mapper:expr) => {
#[bench]
Expand Down
6 changes: 5 additions & 1 deletion library/core/src/slice/mod.rs
Expand Up @@ -2167,7 +2167,11 @@ impl<T> [T] {
// - `mid >= 0`: by definition
// - `mid < size`: `mid = size / 2 + size / 4 + size / 8 ...`
let cmp = f(unsafe { s.get_unchecked(mid) });
base = if cmp == Greater { base } else { mid };
if cmp == Equal {
return Ok(mid);
} else if cmp == Less {
base = mid
}
size -= half;
}
// SAFETY: base is always in [0, size) because base <= mid.
Expand Down
8 changes: 4 additions & 4 deletions library/core/tests/slice.rs
Expand Up @@ -73,13 +73,13 @@ fn test_binary_search_implementation_details() {
let b = [1, 1, 2, 2, 3, 3, 3];
assert_eq!(b.binary_search(&1), Ok(1));
assert_eq!(b.binary_search(&2), Ok(3));
assert_eq!(b.binary_search(&3), Ok(6));
assert_eq!(b.binary_search(&3), Ok(5));
let b = [1, 1, 1, 1, 1, 3, 3, 3, 3];
assert_eq!(b.binary_search(&1), Ok(4));
assert_eq!(b.binary_search(&3), Ok(8));
assert_eq!(b.binary_search(&3), Ok(6));
let b = [1, 1, 1, 1, 3, 3, 3, 3, 3];
assert_eq!(b.binary_search(&1), Ok(3));
assert_eq!(b.binary_search(&3), Ok(8));
assert_eq!(b.binary_search(&1), Ok(2));
assert_eq!(b.binary_search(&3), Ok(4));
}

#[test]
Expand Down

0 comments on commit 18e44a1

Please sign in to comment.