Add a second level cache to contain tx batch specific programs (#31413)

* Add a second level cache to contain tx batch specific programs

* fix clippy

* address review comments
This commit is contained in:
Pankaj Garg 2023-05-01 09:09:43 -07:00 committed by GitHub
parent 427ad7b5bd
commit 8eebf64464
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 86 additions and 44 deletions

View File

@ -262,6 +262,38 @@ pub struct LoadedPrograms {
entries: HashMap<Pubkey, Vec<Arc<LoadedProgram>>>,
}
#[derive(Debug, Default)]
pub struct LoadedProgramsForTxBatch {
/// Pubkey is the address of a program.
/// LoadedProgram is the corresponding program entry valid for the slot in which a transaction is being executed.
entries: HashMap<Pubkey, Arc<LoadedProgram>>,
slot: Slot,
}
impl LoadedProgramsForTxBatch {
/// Refill the cache with a single entry. It's typically called during transaction loading, and
/// transaction processing (for program management instructions).
/// The replaces the existing entry (if any) with the provided entry. The return value contains
/// `true` if an entry existed.
/// The function also returns the newly inserted value.
pub fn replenish(
&mut self,
key: Pubkey,
entry: Arc<LoadedProgram>,
) -> (bool, Arc<LoadedProgram>) {
debug_assert!(entry.effective_slot <= self.slot);
(self.entries.insert(key, entry.clone()).is_some(), entry)
}
pub fn find(&self, key: Pubkey) -> Option<Arc<LoadedProgram>> {
self.entries.get(&key).cloned()
}
pub fn slot(&self) -> Slot {
self.slot
}
}
#[cfg(RUSTC_WITH_SPECIALIZATION)]
impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms {
fn example() -> Self {
@ -373,7 +405,7 @@ impl LoadedPrograms {
&self,
working_slot: &S,
keys: impl Iterator<Item = (Pubkey, LoadedProgramMatchCriteria)>,
) -> (HashMap<Pubkey, Arc<LoadedProgram>>, Vec<Pubkey>) {
) -> (LoadedProgramsForTxBatch, Vec<Pubkey>) {
let mut missing = Vec::new();
let found = keys
.filter_map(|(key, match_criteria)| {
@ -410,7 +442,13 @@ impl LoadedPrograms {
None
})
.collect();
(found, missing)
(
LoadedProgramsForTxBatch {
entries: found,
slot: working_slot.current_slot(),
},
missing,
)
}
/// Evicts programs which were used infrequently
@ -528,13 +566,12 @@ mod tests {
use {
crate::loaded_programs::{
BlockRelation, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType,
LoadedPrograms, WorkingSlot,
LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot,
},
percentage::Percentage,
solana_rbpf::vm::BuiltInProgram,
solana_sdk::{clock::Slot, pubkey::Pubkey},
std::{
collections::HashMap,
ops::ControlFlow,
sync::{
atomic::{AtomicU64, Ordering},
@ -1106,12 +1143,14 @@ mod tests {
}
fn match_slot(
table: &HashMap<Pubkey, Arc<LoadedProgram>>,
table: &LoadedProgramsForTxBatch,
program: &Pubkey,
deployment_slot: Slot,
working_slot: Slot,
) -> bool {
assert_eq!(table.slot, working_slot);
table
.get(program)
.find(*program)
.map(|entry| entry.deployment_slot == deployment_slot)
.unwrap_or(false)
}
@ -1189,8 +1228,8 @@ mod tests {
.into_iter(),
);
assert!(match_slot(&found, &program1, 20));
assert!(match_slot(&found, &program4, 0));
assert!(match_slot(&found, &program1, 20, 22));
assert!(match_slot(&found, &program4, 0, 22));
assert!(missing.contains(&program2));
assert!(missing.contains(&program3));
@ -1208,11 +1247,11 @@ mod tests {
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
assert!(match_slot(&found, &program2, 11));
assert!(match_slot(&found, &program1, 0, 16));
assert!(match_slot(&found, &program2, 11, 16));
// The effective slot of program4 deployed in slot 15 is 19. So it should not be usable in slot 16.
assert!(match_slot(&found, &program4, 5));
assert!(match_slot(&found, &program4, 5, 16));
assert!(missing.contains(&program3));
@ -1229,11 +1268,11 @@ mod tests {
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
assert!(match_slot(&found, &program2, 11));
assert!(match_slot(&found, &program1, 0, 18));
assert!(match_slot(&found, &program2, 11, 18));
// The effective slot of program4 deployed in slot 15 is 18. So it should be usable in slot 18.
assert!(match_slot(&found, &program4, 15));
assert!(match_slot(&found, &program4, 15, 18));
assert!(missing.contains(&program3));
@ -1250,11 +1289,11 @@ mod tests {
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
assert!(match_slot(&found, &program2, 11));
assert!(match_slot(&found, &program1, 0, 23));
assert!(match_slot(&found, &program2, 11, 23));
// The effective slot of program4 deployed in slot 15 is 19. So it should be usable in slot 23.
assert!(match_slot(&found, &program4, 15));
assert!(match_slot(&found, &program4, 15, 23));
assert!(missing.contains(&program3));
@ -1271,9 +1310,9 @@ mod tests {
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
assert!(match_slot(&found, &program2, 5));
assert!(match_slot(&found, &program4, 5));
assert!(match_slot(&found, &program1, 0, 11));
assert!(match_slot(&found, &program2, 5, 11));
assert!(match_slot(&found, &program4, 5, 11));
assert!(missing.contains(&program3));
@ -1301,10 +1340,10 @@ mod tests {
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
assert!(match_slot(&found, &program2, 11));
assert!(match_slot(&found, &program1, 0, 19));
assert!(match_slot(&found, &program2, 11, 19));
// Program4 deployed at slot 19 should not be expired yet
assert!(match_slot(&found, &program4, 19));
assert!(match_slot(&found, &program4, 19, 19));
assert!(missing.contains(&program3));
@ -1322,8 +1361,8 @@ mod tests {
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
assert!(match_slot(&found, &program2, 11));
assert!(match_slot(&found, &program1, 0, 21));
assert!(match_slot(&found, &program2, 11, 21));
assert!(missing.contains(&program3));
assert!(missing.contains(&program4));
@ -1364,8 +1403,8 @@ mod tests {
);
// Since the fork was pruned, we should not find the entry deployed at slot 20.
assert!(match_slot(&found, &program1, 0));
assert!(match_slot(&found, &program4, 0));
assert!(match_slot(&found, &program1, 0, 22));
assert!(match_slot(&found, &program4, 0, 22));
assert!(missing.contains(&program2));
assert!(missing.contains(&program3));
@ -1383,10 +1422,10 @@ mod tests {
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
assert!(match_slot(&found, &program2, 11));
assert!(match_slot(&found, &program3, 25));
assert!(match_slot(&found, &program4, 5));
assert!(match_slot(&found, &program1, 0, 27));
assert!(match_slot(&found, &program2, 11, 27));
assert!(match_slot(&found, &program3, 25, 27));
assert!(match_slot(&found, &program4, 5, 27));
cache.prune(&fork_graph, 15);
@ -1418,9 +1457,9 @@ mod tests {
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
assert!(match_slot(&found, &program2, 11));
assert!(match_slot(&found, &program4, 5));
assert!(match_slot(&found, &program1, 0, 27));
assert!(match_slot(&found, &program2, 11, 27));
assert!(match_slot(&found, &program4, 5, 27));
// program3 was deployed on slot 25, which has been pruned
assert!(missing.contains(&program3));
@ -1473,8 +1512,8 @@ mod tests {
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
assert!(match_slot(&found, &program2, 11));
assert!(match_slot(&found, &program1, 0, 12));
assert!(match_slot(&found, &program2, 11, 12));
assert!(missing.contains(&program3));
@ -1495,7 +1534,7 @@ mod tests {
.into_iter(),
);
assert!(match_slot(&found, &program2, 11));
assert!(match_slot(&found, &program2, 11, 12));
assert!(missing.contains(&program1));
assert!(missing.contains(&program3));
@ -1560,8 +1599,8 @@ mod tests {
);
// Program1 deployed at slot 11 should not be expired yet
assert!(match_slot(&found, &program1, 11));
assert!(match_slot(&found, &program2, 11));
assert!(match_slot(&found, &program1, 11, 12));
assert!(match_slot(&found, &program2, 11, 12));
assert!(missing.contains(&program3));
@ -1578,7 +1617,7 @@ mod tests {
.into_iter(),
);
assert!(match_slot(&found, &program2, 11));
assert!(match_slot(&found, &program2, 11, 15));
assert!(missing.contains(&program1));
assert!(missing.contains(&program3));
@ -1642,6 +1681,7 @@ mod tests {
// The cache should have the program deployed at slot 0
assert_eq!(
found
.entries
.get(&program1)
.expect("Did not find the program")
.deployment_slot,

View File

@ -97,7 +97,10 @@ use {
builtin_program::{BuiltinProgram, BuiltinPrograms, ProcessInstructionWithContext},
compute_budget::{self, ComputeBudget},
executor_cache::{BankExecutorCache, TransactionExecutorCache, MAX_CACHED_EXECUTORS},
loaded_programs::{LoadedProgram, LoadedProgramType, LoadedPrograms, WorkingSlot},
loaded_programs::{
LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType, LoadedPrograms,
LoadedProgramsForTxBatch, WorkingSlot,
},
log_collector::LogCollector,
sysvar_cache::SysvarCache,
timings::{ExecuteTimingType, ExecuteTimings},
@ -258,7 +261,6 @@ pub struct BankRc {
#[cfg(RUSTC_WITH_SPECIALIZATION)]
use solana_frozen_abi::abi_example::AbiExample;
use solana_program_runtime::loaded_programs::LoadedProgramMatchCriteria;
#[cfg(RUSTC_WITH_SPECIALIZATION)]
impl AbiExample for BankRc {
@ -4427,7 +4429,7 @@ impl Bank {
fn load_and_get_programs_from_cache(
&self,
program_accounts_map: &HashMap<Pubkey, &Pubkey>,
) -> HashMap<Pubkey, Arc<LoadedProgram>> {
) -> LoadedProgramsForTxBatch {
let programs_and_slots: Vec<(Pubkey, LoadedProgramMatchCriteria)> =
if self.check_program_modification_slot {
program_accounts_map
@ -4476,7 +4478,7 @@ impl Bank {
for (key, program) in missing_programs {
let (_was_occupied, entry) = loaded_programs_cache.replenish(key, program);
// Use the returned entry as that might have been deduplicated globally
loaded_programs_for_txs.insert(key, entry);
loaded_programs_for_txs.replenish(key, entry);
}
loaded_programs_for_txs