bank: prime new executor cache entry use-counts

This commit is contained in:
Trent Nelson 2022-01-06 03:50:06 -07:00 committed by mergify[bot]
parent 2759cb860b
commit 4ce48307bb
1 changed files with 100 additions and 27 deletions

View File

@ -66,6 +66,7 @@ use {
dashmap::DashMap,
itertools::Itertools,
log::*,
rand::Rng,
rayon::{
iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator},
ThreadPool, ThreadPoolBuilder,
@ -144,7 +145,7 @@ use {
collections::{HashMap, HashSet},
convert::{TryFrom, TryInto},
fmt, mem,
ops::RangeInclusive,
ops::{Div, RangeInclusive},
path::PathBuf,
ptr,
rc::Rc,
@ -390,29 +391,32 @@ impl CachedExecutors {
entry
} else {
saturating_add_assign!(self.stats.misses, 1);
if self.executors.len() >= self.max {
let mut least = u64::MAX;
let default_key = Pubkey::default();
let mut least_key = &default_key;
for (key, entry) in self.executors.iter() {
let mut counts = self
.executors
.iter()
.map(|(key, entry)| {
let count = entry.prev_epoch_count + entry.epoch_count.load(Relaxed);
if count < least {
least = count;
least_key = key;
}
(key, count)
})
.collect::<Vec<_>>();
counts.sort_unstable_by_key(|(_, count)| *count);
let primer_count = Self::get_primer_count(counts.as_slice());
if self.executors.len() >= self.max {
if let Some(least_key) = counts.first().map(|least| *least.0) {
let _ = self.executors.remove(&least_key);
self.stats
.evictions
.entry(least_key)
.and_modify(|c| saturating_add_assign!(*c, 1))
.or_insert(1);
}
let least_key = *least_key;
let _ = self.executors.remove(&least_key);
self.stats
.evictions
.entry(least_key)
.and_modify(|c| saturating_add_assign!(*c, 1))
.or_insert(1);
}
CachedExecutorsEntry {
prev_epoch_count: 0,
epoch_count: AtomicU64::new(0),
epoch_count: AtomicU64::new(primer_count),
executor,
}
};
@ -422,6 +426,38 @@ impl CachedExecutors {
fn remove(&mut self, pubkey: &Pubkey) {
let _ = self.executors.remove(pubkey);
}
fn get_primer_count_upper_bound_inclusive(counts: &[(&Pubkey, u64)]) -> u64 {
const PRIMER_COUNT_TARGET_PERCENTILE: u64 = 85;
#[allow(clippy::assertions_on_constants)]
{
assert!(PRIMER_COUNT_TARGET_PERCENTILE <= 100);
}
// Executor use-frequencies are assumed to fit a Pareto distribution. Choose an
// upper-bound for our primer count as the actual count at the target rank to avoid
// an upward bias
let target_index = u64::try_from(counts.len().saturating_sub(1))
.ok()
.and_then(|counts| {
let index = counts
.saturating_mul(PRIMER_COUNT_TARGET_PERCENTILE)
.div(100); // switch to u64::saturating_div once stable
usize::try_from(index).ok()
})
.unwrap_or(0);
counts
.get(target_index)
.map(|(_, count)| *count)
.unwrap_or(0)
}
fn get_primer_count(counts: &[(&Pubkey, u64)]) -> u64 {
let max_primer_count = Self::get_primer_count_upper_bound_inclusive(counts);
let mut rng = rand::thread_rng();
rng.gen_range(0, max_primer_count.saturating_add(1))
}
}
#[derive(Debug)]
@ -12806,19 +12842,25 @@ pub(crate) mod tests {
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key2).is_some());
cache.put(&key4, executor.clone());
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key2).is_some());
assert!(cache.get(&key3).is_none());
assert!(cache.get(&key4).is_some());
let num_retained = [&key1, &key2, &key3]
.iter()
.map(|key| cache.get(key))
.flatten()
.count();
assert_eq!(num_retained, 2);
assert!(cache.get(&key4).is_some());
assert!(cache.get(&key4).is_some());
assert!(cache.get(&key4).is_some());
cache.put(&key3, executor.clone());
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key2).is_none());
assert!(cache.get(&key3).is_some());
assert!(cache.get(&key4).is_some());
let num_retained = [&key1, &key2, &key4]
.iter()
.map(|key| cache.get(key))
.flatten()
.count();
assert_eq!(num_retained, 2);
}
#[test]
@ -12847,12 +12889,23 @@ pub(crate) mod tests {
Arc::make_mut(&mut cache).put(&key4, executor.clone());
assert!(cache.get(&key4).is_some());
assert!(cache.get(&key3).is_none());
let num_retained = [&key1, &key2, &key3]
.iter()
.map(|key| cache.get(key))
.flatten()
.count();
assert_eq!(num_retained, 2);
Arc::make_mut(&mut cache).put(&key1, executor.clone());
Arc::make_mut(&mut cache).put(&key3, executor.clone());
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key4).is_none());
assert!(cache.get(&key3).is_some());
let num_retained = [&key2, &key4]
.iter()
.map(|key| cache.get(key))
.flatten()
.count();
assert_eq!(num_retained, 1);
cache = cache.clone_with_epoch(2);
assert!(cache.current_epoch == 2);
@ -15398,4 +15451,24 @@ pub(crate) mod tests {
))
));
}
#[test]
fn test_executor_cache_get_primer_count_upper_bound_inclusive() {
let pubkey = Pubkey::default();
let v = [];
assert_eq!(
CachedExecutors::get_primer_count_upper_bound_inclusive(&v),
0
);
let v = [(&pubkey, 1)];
assert_eq!(
CachedExecutors::get_primer_count_upper_bound_inclusive(&v),
1
);
let v = (0u64..10).map(|i| (&pubkey, i)).collect::<Vec<_>>();
assert_eq!(
CachedExecutors::get_primer_count_upper_bound_inclusive(v.as_slice()),
7
);
}
}