diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 5567211296..bdd223f2cf 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -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::>(); + 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::>(); + assert_eq!( + CachedExecutors::get_primer_count_upper_bound_inclusive(v.as_slice()), + 7 + ); + } }