Do not evict unloaded programs (#31465)

* Do not evict unloaded programs

* cleanup
This commit is contained in:
Pankaj Garg 2023-05-04 07:49:54 -07:00 committed by GitHub
parent 22d4c6abf2
commit 96e170bd37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 18 additions and 137 deletions

View File

@ -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)

View File

@ -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={}",