Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

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

Merged
merged 3 commits into from
May 1, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 80 additions & 40 deletions program-runtime/src/loaded_programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,38 @@ pub struct LoadedPrograms {
entries: HashMap<Pubkey, Vec<Arc<LoadedProgram>>>,
}

#[derive(Debug, Default)]
pub struct LoadedProgramsForSlot {
/// 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 LoadedProgramsForSlot {
/// 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 {
Expand Down Expand Up @@ -373,7 +405,7 @@ impl LoadedPrograms {
&self,
working_slot: &S,
keys: impl Iterator<Item = (Pubkey, LoadedProgramMatchCriteria)>,
) -> (HashMap<Pubkey, Arc<LoadedProgram>>, Vec<Pubkey>) {
) -> (LoadedProgramsForSlot, Vec<Pubkey>) {
let mut missing = Vec::new();
let found = keys
.filter_map(|(key, match_criteria)| {
Expand Down Expand Up @@ -410,7 +442,13 @@ impl LoadedPrograms {
None
})
.collect();
(found, missing)
(
LoadedProgramsForSlot {
entries: found,
slot: working_slot.current_slot(),
},
missing,
)
}

/// Evicts programs which were used infrequently
Expand Down Expand Up @@ -528,13 +566,12 @@ mod tests {
use {
crate::loaded_programs::{
BlockRelation, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType,
LoadedPrograms, WorkingSlot,
LoadedPrograms, LoadedProgramsForSlot, WorkingSlot,
},
percentage::Percentage,
solana_rbpf::vm::BuiltInProgram,
solana_sdk::{clock::Slot, pubkey::Pubkey},
std::{
collections::HashMap,
ops::ControlFlow,
sync::{
atomic::{AtomicU64, Ordering},
Expand Down Expand Up @@ -1106,12 +1143,14 @@ mod tests {
}

fn match_slot(
table: &HashMap<Pubkey, Arc<LoadedProgram>>,
table: &LoadedProgramsForSlot,
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)
}
Expand Down Expand Up @@ -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));
Expand All @@ -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));

Expand All @@ -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));

Expand All @@ -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));

Expand All @@ -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));

Expand Down Expand Up @@ -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));

Expand All @@ -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));
Expand Down Expand Up @@ -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));
Expand All @@ -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);

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));

Expand All @@ -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));
Expand Down Expand Up @@ -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));

Expand All @@ -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));
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 6 additions & 4 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
LoadedProgramsForSlot, WorkingSlot,
},
log_collector::LogCollector,
sysvar_cache::SysvarCache,
timings::{ExecuteTimingType, ExecuteTimings},
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -4423,7 +4425,7 @@ impl Bank {
fn load_and_get_programs_from_cache(
&self,
program_accounts_map: &HashMap<Pubkey, &Pubkey>,
) -> HashMap<Pubkey, Arc<LoadedProgram>> {
) -> LoadedProgramsForSlot {
let programs_and_slots: Vec<(Pubkey, LoadedProgramMatchCriteria)> =
if self.check_program_modification_slot {
program_accounts_map
Expand Down Expand Up @@ -4472,7 +4474,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
Expand Down