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,
|
pubkey::Pubkey, saturating_add_assign,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
cmp,
|
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt::{Debug, Formatter},
|
fmt::{Debug, Formatter},
|
||||||
sync::{
|
sync::{
|
||||||
|
@ -26,7 +25,6 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_LOADED_ENTRY_COUNT: usize = 256;
|
const MAX_LOADED_ENTRY_COUNT: usize = 256;
|
||||||
const MAX_UNLOADED_ENTRY_COUNT: usize = 1024;
|
|
||||||
|
|
||||||
/// Relationship between two fork IDs
|
/// Relationship between two fork IDs
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
@ -444,15 +442,9 @@ impl LoadedPrograms {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evicts programs which were used infrequently
|
/// Unloads programs which were used infrequently
|
||||||
pub fn sort_and_evict(&mut self, shrink_to: PercentageInteger) {
|
pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
|
||||||
let mut num_loaded: usize = 0;
|
let sorted_candidates: Vec<(Pubkey, Arc<LoadedProgram>)> = self
|
||||||
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
|
|
||||||
.entries
|
.entries
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(id, list)| {
|
.flat_map(|(id, list)| {
|
||||||
|
@ -460,47 +452,23 @@ impl LoadedPrograms {
|
||||||
.filter_map(move |program| match program.program {
|
.filter_map(move |program| match program.program {
|
||||||
LoadedProgramType::LegacyV0(_)
|
LoadedProgramType::LegacyV0(_)
|
||||||
| LoadedProgramType::LegacyV1(_)
|
| LoadedProgramType::LegacyV1(_)
|
||||||
| LoadedProgramType::Typed(_) => Some((0, (*id, program.clone()))),
|
| LoadedProgramType::Typed(_) => Some((*id, program.clone())),
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
LoadedProgramType::TestLoaded => Some((0, (*id, program.clone()))),
|
LoadedProgramType::TestLoaded => Some((*id, program.clone())),
|
||||||
LoadedProgramType::Unloaded => Some((1, (*id, program.clone()))),
|
LoadedProgramType::Unloaded
|
||||||
LoadedProgramType::FailedVerification
|
| LoadedProgramType::FailedVerification
|
||||||
| LoadedProgramType::Closed
|
| LoadedProgramType::Closed
|
||||||
| LoadedProgramType::DelayVisibility
|
| LoadedProgramType::DelayVisibility
|
||||||
| LoadedProgramType::Builtin(_, _) => None,
|
| LoadedProgramType::Builtin(_, _) => None,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.sorted_by_cached_key(|(order, (_id, program))| {
|
.sorted_by_cached_key(|(_id, program)| program.usage_counter.load(Ordering::Relaxed))
|
||||||
(*order, program.usage_counter.load(Ordering::Relaxed))
|
.collect();
|
||||||
})
|
|
||||||
.unzip();
|
|
||||||
|
|
||||||
for order in ordering {
|
let num_to_unload = sorted_candidates
|
||||||
match order {
|
.len()
|
||||||
0 => num_loaded = num_loaded.saturating_add(1),
|
.saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
|
||||||
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));
|
|
||||||
self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
|
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();
|
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) {
|
fn remove_expired_entries(&mut self, current_slot: Slot) {
|
||||||
for entry in self.entries.values_mut() {
|
for entry in self.entries.values_mut() {
|
||||||
entry.retain(|program| {
|
entry.retain(|program| {
|
||||||
|
@ -759,9 +713,9 @@ mod tests {
|
||||||
|
|
||||||
// Evicting to 2% should update cache with
|
// Evicting to 2% should update cache with
|
||||||
// * 5 active entries
|
// * 5 active entries
|
||||||
// * 20 unloaded entries
|
// * 33 unloaded entries (3 active programs will get unloaded)
|
||||||
// * 30 tombstones (tombstones are not evicted)
|
// * 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.
|
// Check that every program is still in the cache.
|
||||||
programs.iter().for_each(|entry| {
|
programs.iter().for_each(|entry| {
|
||||||
assert!(cache.entries.get(&entry.0).is_some());
|
assert!(cache.entries.get(&entry.0).is_some());
|
||||||
|
@ -799,83 +753,10 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(num_loaded, 5);
|
assert_eq!(num_loaded, 5);
|
||||||
assert_eq!(num_unloaded, 20);
|
assert_eq!(num_unloaded, 33);
|
||||||
assert_eq!(num_tombstones, 30);
|
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]
|
#[test]
|
||||||
fn test_usage_count_of_unloaded_program() {
|
fn test_usage_count_of_unloaded_program() {
|
||||||
let mut cache = LoadedPrograms::default();
|
let mut cache = LoadedPrograms::default();
|
||||||
|
@ -890,7 +771,7 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
// This will unload the program deployed at slot 0, with usage count = 10
|
// 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| {
|
let num_unloaded = num_matching_entries(&cache, |program_type| {
|
||||||
matches!(program_type, LoadedProgramType::Unloaded)
|
matches!(program_type, LoadedProgramType::Unloaded)
|
||||||
|
|
|
@ -4714,11 +4714,11 @@ impl Bank {
|
||||||
|
|
||||||
execution_time.stop();
|
execution_time.stop();
|
||||||
|
|
||||||
const EVICT_CACHE_TO_PERCENTAGE: u8 = 90;
|
const SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE: u8 = 90;
|
||||||
self.loaded_programs_cache
|
self.loaded_programs_cache
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.sort_and_evict(Percentage::from(EVICT_CACHE_TO_PERCENTAGE));
|
.sort_and_unload(Percentage::from(SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE));
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"check: {}us load: {}us execute: {}us txs_len={}",
|
"check: {}us load: {}us execute: {}us txs_len={}",
|
||||||
|
|
Loading…
Reference in New Issue