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:
parent
427ad7b5bd
commit
8eebf64464
|
@ -262,6 +262,38 @@ pub struct LoadedPrograms {
|
||||||
entries: HashMap<Pubkey, Vec<Arc<LoadedProgram>>>,
|
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)]
|
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||||
impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms {
|
impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms {
|
||||||
fn example() -> Self {
|
fn example() -> Self {
|
||||||
|
@ -373,7 +405,7 @@ impl LoadedPrograms {
|
||||||
&self,
|
&self,
|
||||||
working_slot: &S,
|
working_slot: &S,
|
||||||
keys: impl Iterator<Item = (Pubkey, LoadedProgramMatchCriteria)>,
|
keys: impl Iterator<Item = (Pubkey, LoadedProgramMatchCriteria)>,
|
||||||
) -> (HashMap<Pubkey, Arc<LoadedProgram>>, Vec<Pubkey>) {
|
) -> (LoadedProgramsForTxBatch, Vec<Pubkey>) {
|
||||||
let mut missing = Vec::new();
|
let mut missing = Vec::new();
|
||||||
let found = keys
|
let found = keys
|
||||||
.filter_map(|(key, match_criteria)| {
|
.filter_map(|(key, match_criteria)| {
|
||||||
|
@ -410,7 +442,13 @@ impl LoadedPrograms {
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(found, missing)
|
(
|
||||||
|
LoadedProgramsForTxBatch {
|
||||||
|
entries: found,
|
||||||
|
slot: working_slot.current_slot(),
|
||||||
|
},
|
||||||
|
missing,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evicts programs which were used infrequently
|
/// Evicts programs which were used infrequently
|
||||||
|
@ -528,13 +566,12 @@ mod tests {
|
||||||
use {
|
use {
|
||||||
crate::loaded_programs::{
|
crate::loaded_programs::{
|
||||||
BlockRelation, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType,
|
BlockRelation, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType,
|
||||||
LoadedPrograms, WorkingSlot,
|
LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot,
|
||||||
},
|
},
|
||||||
percentage::Percentage,
|
percentage::Percentage,
|
||||||
solana_rbpf::vm::BuiltInProgram,
|
solana_rbpf::vm::BuiltInProgram,
|
||||||
solana_sdk::{clock::Slot, pubkey::Pubkey},
|
solana_sdk::{clock::Slot, pubkey::Pubkey},
|
||||||
std::{
|
std::{
|
||||||
collections::HashMap,
|
|
||||||
ops::ControlFlow,
|
ops::ControlFlow,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicU64, Ordering},
|
atomic::{AtomicU64, Ordering},
|
||||||
|
@ -1106,12 +1143,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_slot(
|
fn match_slot(
|
||||||
table: &HashMap<Pubkey, Arc<LoadedProgram>>,
|
table: &LoadedProgramsForTxBatch,
|
||||||
program: &Pubkey,
|
program: &Pubkey,
|
||||||
deployment_slot: Slot,
|
deployment_slot: Slot,
|
||||||
|
working_slot: Slot,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
assert_eq!(table.slot, working_slot);
|
||||||
table
|
table
|
||||||
.get(program)
|
.find(*program)
|
||||||
.map(|entry| entry.deployment_slot == deployment_slot)
|
.map(|entry| entry.deployment_slot == deployment_slot)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
@ -1189,8 +1228,8 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 20));
|
assert!(match_slot(&found, &program1, 20, 22));
|
||||||
assert!(match_slot(&found, &program4, 0));
|
assert!(match_slot(&found, &program4, 0, 22));
|
||||||
|
|
||||||
assert!(missing.contains(&program2));
|
assert!(missing.contains(&program2));
|
||||||
assert!(missing.contains(&program3));
|
assert!(missing.contains(&program3));
|
||||||
|
@ -1208,11 +1247,11 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0));
|
assert!(match_slot(&found, &program1, 0, 16));
|
||||||
assert!(match_slot(&found, &program2, 11));
|
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.
|
// 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));
|
assert!(missing.contains(&program3));
|
||||||
|
|
||||||
|
@ -1229,11 +1268,11 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0));
|
assert!(match_slot(&found, &program1, 0, 18));
|
||||||
assert!(match_slot(&found, &program2, 11));
|
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.
|
// 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));
|
assert!(missing.contains(&program3));
|
||||||
|
|
||||||
|
@ -1250,11 +1289,11 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0));
|
assert!(match_slot(&found, &program1, 0, 23));
|
||||||
assert!(match_slot(&found, &program2, 11));
|
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.
|
// 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));
|
assert!(missing.contains(&program3));
|
||||||
|
|
||||||
|
@ -1271,9 +1310,9 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0));
|
assert!(match_slot(&found, &program1, 0, 11));
|
||||||
assert!(match_slot(&found, &program2, 5));
|
assert!(match_slot(&found, &program2, 5, 11));
|
||||||
assert!(match_slot(&found, &program4, 5));
|
assert!(match_slot(&found, &program4, 5, 11));
|
||||||
|
|
||||||
assert!(missing.contains(&program3));
|
assert!(missing.contains(&program3));
|
||||||
|
|
||||||
|
@ -1301,10 +1340,10 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0));
|
assert!(match_slot(&found, &program1, 0, 19));
|
||||||
assert!(match_slot(&found, &program2, 11));
|
assert!(match_slot(&found, &program2, 11, 19));
|
||||||
// Program4 deployed at slot 19 should not be expired yet
|
// 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));
|
assert!(missing.contains(&program3));
|
||||||
|
|
||||||
|
@ -1322,8 +1361,8 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0));
|
assert!(match_slot(&found, &program1, 0, 21));
|
||||||
assert!(match_slot(&found, &program2, 11));
|
assert!(match_slot(&found, &program2, 11, 21));
|
||||||
|
|
||||||
assert!(missing.contains(&program3));
|
assert!(missing.contains(&program3));
|
||||||
assert!(missing.contains(&program4));
|
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.
|
// 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, &program1, 0, 22));
|
||||||
assert!(match_slot(&found, &program4, 0));
|
assert!(match_slot(&found, &program4, 0, 22));
|
||||||
|
|
||||||
assert!(missing.contains(&program2));
|
assert!(missing.contains(&program2));
|
||||||
assert!(missing.contains(&program3));
|
assert!(missing.contains(&program3));
|
||||||
|
@ -1383,10 +1422,10 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0));
|
assert!(match_slot(&found, &program1, 0, 27));
|
||||||
assert!(match_slot(&found, &program2, 11));
|
assert!(match_slot(&found, &program2, 11, 27));
|
||||||
assert!(match_slot(&found, &program3, 25));
|
assert!(match_slot(&found, &program3, 25, 27));
|
||||||
assert!(match_slot(&found, &program4, 5));
|
assert!(match_slot(&found, &program4, 5, 27));
|
||||||
|
|
||||||
cache.prune(&fork_graph, 15);
|
cache.prune(&fork_graph, 15);
|
||||||
|
|
||||||
|
@ -1418,9 +1457,9 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0));
|
assert!(match_slot(&found, &program1, 0, 27));
|
||||||
assert!(match_slot(&found, &program2, 11));
|
assert!(match_slot(&found, &program2, 11, 27));
|
||||||
assert!(match_slot(&found, &program4, 5));
|
assert!(match_slot(&found, &program4, 5, 27));
|
||||||
|
|
||||||
// program3 was deployed on slot 25, which has been pruned
|
// program3 was deployed on slot 25, which has been pruned
|
||||||
assert!(missing.contains(&program3));
|
assert!(missing.contains(&program3));
|
||||||
|
@ -1473,8 +1512,8 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0));
|
assert!(match_slot(&found, &program1, 0, 12));
|
||||||
assert!(match_slot(&found, &program2, 11));
|
assert!(match_slot(&found, &program2, 11, 12));
|
||||||
|
|
||||||
assert!(missing.contains(&program3));
|
assert!(missing.contains(&program3));
|
||||||
|
|
||||||
|
@ -1495,7 +1534,7 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program2, 11));
|
assert!(match_slot(&found, &program2, 11, 12));
|
||||||
|
|
||||||
assert!(missing.contains(&program1));
|
assert!(missing.contains(&program1));
|
||||||
assert!(missing.contains(&program3));
|
assert!(missing.contains(&program3));
|
||||||
|
@ -1560,8 +1599,8 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Program1 deployed at slot 11 should not be expired yet
|
// Program1 deployed at slot 11 should not be expired yet
|
||||||
assert!(match_slot(&found, &program1, 11));
|
assert!(match_slot(&found, &program1, 11, 12));
|
||||||
assert!(match_slot(&found, &program2, 11));
|
assert!(match_slot(&found, &program2, 11, 12));
|
||||||
|
|
||||||
assert!(missing.contains(&program3));
|
assert!(missing.contains(&program3));
|
||||||
|
|
||||||
|
@ -1578,7 +1617,7 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program2, 11));
|
assert!(match_slot(&found, &program2, 11, 15));
|
||||||
|
|
||||||
assert!(missing.contains(&program1));
|
assert!(missing.contains(&program1));
|
||||||
assert!(missing.contains(&program3));
|
assert!(missing.contains(&program3));
|
||||||
|
@ -1642,6 +1681,7 @@ mod tests {
|
||||||
// The cache should have the program deployed at slot 0
|
// The cache should have the program deployed at slot 0
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
found
|
found
|
||||||
|
.entries
|
||||||
.get(&program1)
|
.get(&program1)
|
||||||
.expect("Did not find the program")
|
.expect("Did not find the program")
|
||||||
.deployment_slot,
|
.deployment_slot,
|
||||||
|
|
|
@ -97,7 +97,10 @@ use {
|
||||||
builtin_program::{BuiltinProgram, BuiltinPrograms, ProcessInstructionWithContext},
|
builtin_program::{BuiltinProgram, BuiltinPrograms, ProcessInstructionWithContext},
|
||||||
compute_budget::{self, ComputeBudget},
|
compute_budget::{self, ComputeBudget},
|
||||||
executor_cache::{BankExecutorCache, TransactionExecutorCache, MAX_CACHED_EXECUTORS},
|
executor_cache::{BankExecutorCache, TransactionExecutorCache, MAX_CACHED_EXECUTORS},
|
||||||
loaded_programs::{LoadedProgram, LoadedProgramType, LoadedPrograms, WorkingSlot},
|
loaded_programs::{
|
||||||
|
LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType, LoadedPrograms,
|
||||||
|
LoadedProgramsForTxBatch, WorkingSlot,
|
||||||
|
},
|
||||||
log_collector::LogCollector,
|
log_collector::LogCollector,
|
||||||
sysvar_cache::SysvarCache,
|
sysvar_cache::SysvarCache,
|
||||||
timings::{ExecuteTimingType, ExecuteTimings},
|
timings::{ExecuteTimingType, ExecuteTimings},
|
||||||
|
@ -258,7 +261,6 @@ pub struct BankRc {
|
||||||
|
|
||||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||||
use solana_frozen_abi::abi_example::AbiExample;
|
use solana_frozen_abi::abi_example::AbiExample;
|
||||||
use solana_program_runtime::loaded_programs::LoadedProgramMatchCriteria;
|
|
||||||
|
|
||||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||||
impl AbiExample for BankRc {
|
impl AbiExample for BankRc {
|
||||||
|
@ -4427,7 +4429,7 @@ impl Bank {
|
||||||
fn load_and_get_programs_from_cache(
|
fn load_and_get_programs_from_cache(
|
||||||
&self,
|
&self,
|
||||||
program_accounts_map: &HashMap<Pubkey, &Pubkey>,
|
program_accounts_map: &HashMap<Pubkey, &Pubkey>,
|
||||||
) -> HashMap<Pubkey, Arc<LoadedProgram>> {
|
) -> LoadedProgramsForTxBatch {
|
||||||
let programs_and_slots: Vec<(Pubkey, LoadedProgramMatchCriteria)> =
|
let programs_and_slots: Vec<(Pubkey, LoadedProgramMatchCriteria)> =
|
||||||
if self.check_program_modification_slot {
|
if self.check_program_modification_slot {
|
||||||
program_accounts_map
|
program_accounts_map
|
||||||
|
@ -4476,7 +4478,7 @@ impl Bank {
|
||||||
for (key, program) in missing_programs {
|
for (key, program) in missing_programs {
|
||||||
let (_was_occupied, entry) = loaded_programs_cache.replenish(key, program);
|
let (_was_occupied, entry) = loaded_programs_cache.replenish(key, program);
|
||||||
// Use the returned entry as that might have been deduplicated globally
|
// 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
|
loaded_programs_for_txs
|
||||||
|
|
Loading…
Reference in New Issue