Do not evict unloaded programs (#31465)
* Do not evict unloaded programs * cleanup
This commit is contained in:
parent
22d4c6abf2
commit
96e170bd37
|
@ -15,7 +15,6 @@ use {
|
|||
pubkey::Pubkey, saturating_add_assign,
|
||||
},
|
||||
std::{
|
||||
cmp,
|
||||
collections::HashMap,
|
||||
fmt::{Debug, Formatter},
|
||||
sync::{
|
||||
|
@ -26,7 +25,6 @@ use {
|
|||
};
|
||||
|
||||
const MAX_LOADED_ENTRY_COUNT: usize = 256;
|
||||
const MAX_UNLOADED_ENTRY_COUNT: usize = 1024;
|
||||
|
||||
/// Relationship between two fork IDs
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
|
@ -444,15 +442,9 @@ impl LoadedPrograms {
|
|||
)
|
||||
}
|
||||
|
||||
/// Evicts programs which were used infrequently
|
||||
pub fn sort_and_evict(&mut self, shrink_to: PercentageInteger) {
|
||||
let mut num_loaded: usize = 0;
|
||||
let mut num_unloaded: usize = 0;
|
||||
// Find eviction candidates and sort by their type and usage counters.
|
||||
// Sorted result will have the following order:
|
||||
// Loaded entries with ascending order of their usage count
|
||||
// Unloaded entries with ascending order of their usage count
|
||||
let (ordering, sorted_candidates): (Vec<u32>, Vec<(Pubkey, Arc<LoadedProgram>)>) = self
|
||||
/// Unloads programs which were used infrequently
|
||||
pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
|
||||
let sorted_candidates: Vec<(Pubkey, Arc<LoadedProgram>)> = self
|
||||
.entries
|
||||
.iter()
|
||||
.flat_map(|(id, list)| {
|
||||
|
@ -460,47 +452,23 @@ impl LoadedPrograms {
|
|||
.filter_map(move |program| match program.program {
|
||||
LoadedProgramType::LegacyV0(_)
|
||||
| LoadedProgramType::LegacyV1(_)
|
||||
| LoadedProgramType::Typed(_) => Some((0, (*id, program.clone()))),
|
||||
| LoadedProgramType::Typed(_) => Some((*id, program.clone())),
|
||||
#[cfg(test)]
|
||||
LoadedProgramType::TestLoaded => Some((0, (*id, program.clone()))),
|
||||
LoadedProgramType::Unloaded => Some((1, (*id, program.clone()))),
|
||||
LoadedProgramType::FailedVerification
|
||||
LoadedProgramType::TestLoaded => Some((*id, program.clone())),
|
||||
LoadedProgramType::Unloaded
|
||||
| LoadedProgramType::FailedVerification
|
||||
| LoadedProgramType::Closed
|
||||
| LoadedProgramType::DelayVisibility
|
||||
| LoadedProgramType::Builtin(_, _) => None,
|
||||
})
|
||||
})
|
||||
.sorted_by_cached_key(|(order, (_id, program))| {
|
||||
(*order, program.usage_counter.load(Ordering::Relaxed))
|
||||
})
|
||||
.unzip();
|
||||
.sorted_by_cached_key(|(_id, program)| program.usage_counter.load(Ordering::Relaxed))
|
||||
.collect();
|
||||
|
||||
for order in ordering {
|
||||
match order {
|
||||
0 => num_loaded = num_loaded.saturating_add(1),
|
||||
1 => num_unloaded = num_unloaded.saturating_add(1),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let num_to_unload = num_loaded.saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
|
||||
let num_to_unload = sorted_candidates
|
||||
.len()
|
||||
.saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
|
||||
self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
|
||||
|
||||
let num_unloaded_to_evict = num_unloaded
|
||||
.saturating_add(num_to_unload)
|
||||
.saturating_sub(shrink_to.apply_to(MAX_UNLOADED_ENTRY_COUNT));
|
||||
let (newly_unloaded_programs, sorted_candidates) = sorted_candidates.split_at(num_loaded);
|
||||
let num_old_unloaded_to_evict = cmp::min(sorted_candidates.len(), num_unloaded_to_evict);
|
||||
self.remove_program_entries(sorted_candidates.iter().take(num_old_unloaded_to_evict));
|
||||
|
||||
let num_newly_unloaded_to_evict =
|
||||
num_unloaded_to_evict.saturating_sub(sorted_candidates.len());
|
||||
self.remove_program_entries(
|
||||
newly_unloaded_programs
|
||||
.iter()
|
||||
.take(num_newly_unloaded_to_evict),
|
||||
);
|
||||
|
||||
self.remove_programs_with_no_entries();
|
||||
}
|
||||
|
||||
|
@ -511,20 +479,6 @@ impl LoadedPrograms {
|
|||
}
|
||||
}
|
||||
|
||||
fn remove_program_entries<'a>(
|
||||
&mut self,
|
||||
remove: impl Iterator<Item = &'a (Pubkey, Arc<LoadedProgram>)>,
|
||||
) {
|
||||
for (id, program) in remove {
|
||||
if let Some(entries) = self.entries.get_mut(id) {
|
||||
let index = entries.iter().position(|entry| entry == program);
|
||||
if let Some(index) = index {
|
||||
entries.swap_remove(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_expired_entries(&mut self, current_slot: Slot) {
|
||||
for entry in self.entries.values_mut() {
|
||||
entry.retain(|program| {
|
||||
|
@ -759,9 +713,9 @@ mod tests {
|
|||
|
||||
// Evicting to 2% should update cache with
|
||||
// * 5 active entries
|
||||
// * 20 unloaded entries
|
||||
// * 33 unloaded entries (3 active programs will get unloaded)
|
||||
// * 30 tombstones (tombstones are not evicted)
|
||||
cache.sort_and_evict(Percentage::from(2));
|
||||
cache.sort_and_unload(Percentage::from(2));
|
||||
// Check that every program is still in the cache.
|
||||
programs.iter().for_each(|entry| {
|
||||
assert!(cache.entries.get(&entry.0).is_some());
|
||||
|
@ -799,83 +753,10 @@ mod tests {
|
|||
});
|
||||
|
||||
assert_eq!(num_loaded, 5);
|
||||
assert_eq!(num_unloaded, 20);
|
||||
assert_eq!(num_unloaded, 33);
|
||||
assert_eq!(num_tombstones, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eviction_unload_underflow() {
|
||||
// Test: Eviction of unloaded programs requires eviction of newly unloaded programs.
|
||||
// 1. Load 26 programs
|
||||
// 2. Insert 1 unloaded program
|
||||
// Eviction will unload 21 programs.
|
||||
// 2 unloaded programs need to be evicted. So 1 old and 1 new unloaded program will be evicted.
|
||||
|
||||
let mut cache = LoadedPrograms::default();
|
||||
|
||||
let program1 = Pubkey::new_unique();
|
||||
let num_total_programs = 26;
|
||||
(0..num_total_programs).for_each(|i| {
|
||||
cache.replenish(
|
||||
program1,
|
||||
new_test_loaded_program_with_usage(i, i + 2, AtomicU64::new(i)),
|
||||
);
|
||||
});
|
||||
|
||||
let program2 = Pubkey::new_unique();
|
||||
insert_unloaded_program(&mut cache, program2, 26);
|
||||
|
||||
let num_loaded = num_matching_entries(&cache, |program_type| {
|
||||
matches!(program_type, LoadedProgramType::TestLoaded)
|
||||
});
|
||||
let num_unloaded = num_matching_entries(&cache, |program_type| {
|
||||
matches!(program_type, LoadedProgramType::Unloaded)
|
||||
});
|
||||
let num_tombstones = num_matching_entries(&cache, |program_type| {
|
||||
matches!(
|
||||
program_type,
|
||||
LoadedProgramType::DelayVisibility
|
||||
| LoadedProgramType::FailedVerification
|
||||
| LoadedProgramType::Closed
|
||||
)
|
||||
});
|
||||
|
||||
assert_eq!(num_loaded, 26);
|
||||
assert_eq!(num_unloaded, 1);
|
||||
assert_eq!(num_tombstones, 0);
|
||||
|
||||
// Test that program2 exists in the cache. It'll get removed after eviction.
|
||||
assert!(cache.entries.get(&program2).is_some());
|
||||
|
||||
// Evicting to 2% should update cache with
|
||||
// * 5 active entries
|
||||
// * 20 unloaded entries
|
||||
// * 0 tombstones
|
||||
cache.sort_and_evict(Percentage::from(2));
|
||||
|
||||
let num_loaded = num_matching_entries(&cache, |program_type| {
|
||||
matches!(program_type, LoadedProgramType::TestLoaded)
|
||||
});
|
||||
let num_unloaded = num_matching_entries(&cache, |program_type| {
|
||||
matches!(program_type, LoadedProgramType::Unloaded)
|
||||
});
|
||||
let num_tombstones = num_matching_entries(&cache, |program_type| {
|
||||
matches!(
|
||||
program_type,
|
||||
LoadedProgramType::DelayVisibility
|
||||
| LoadedProgramType::FailedVerification
|
||||
| LoadedProgramType::Closed
|
||||
)
|
||||
});
|
||||
|
||||
assert_eq!(num_loaded, 5);
|
||||
assert_eq!(num_unloaded, 20);
|
||||
assert_eq!(num_tombstones, 0);
|
||||
|
||||
// Test that program2 has been removed after eviction.
|
||||
assert!(cache.entries.get(&program2).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_usage_count_of_unloaded_program() {
|
||||
let mut cache = LoadedPrograms::default();
|
||||
|
@ -890,7 +771,7 @@ mod tests {
|
|||
});
|
||||
|
||||
// This will unload the program deployed at slot 0, with usage count = 10
|
||||
cache.sort_and_evict(Percentage::from(2));
|
||||
cache.sort_and_unload(Percentage::from(2));
|
||||
|
||||
let num_unloaded = num_matching_entries(&cache, |program_type| {
|
||||
matches!(program_type, LoadedProgramType::Unloaded)
|
||||
|
|
|
@ -4714,11 +4714,11 @@ impl Bank {
|
|||
|
||||
execution_time.stop();
|
||||
|
||||
const EVICT_CACHE_TO_PERCENTAGE: u8 = 90;
|
||||
const SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE: u8 = 90;
|
||||
self.loaded_programs_cache
|
||||
.write()
|
||||
.unwrap()
|
||||
.sort_and_evict(Percentage::from(EVICT_CACHE_TO_PERCENTAGE));
|
||||
.sort_and_unload(Percentage::from(SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE));
|
||||
|
||||
debug!(
|
||||
"check: {}us load: {}us execute: {}us txs_len={}",
|
||||
|
|
Loading…
Reference in New Issue