Skip to content

Commit

Permalink
bank: prime new executor cache entry use-counts
Browse files Browse the repository at this point in the history
(cherry picked from commit 4ce4830)

# Conflicts:
#	runtime/src/bank.rs
  • Loading branch information
t-nelson authored and mergify-bot committed Jan 8, 2022
1 parent 6130466 commit 4d085d6
Showing 1 changed file with 147 additions and 27 deletions.
174 changes: 147 additions & 27 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ use {
dashmap::DashMap,
itertools::Itertools,
log::*,
rand::Rng,
rayon::{
iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator},
ThreadPool, ThreadPoolBuilder,
Expand Down Expand Up @@ -132,7 +133,7 @@ use {
collections::{HashMap, HashSet},
convert::{TryFrom, TryInto},
fmt, mem,
ops::RangeInclusive,
ops::{Div, RangeInclusive},
path::PathBuf,
ptr,
rc::Rc,
Expand Down Expand Up @@ -449,29 +450,32 @@ impl CachedExecutors {
entry
} else {
saturating_add_assign!(self.stats.misses, 1);
if self.executors.len() >= self.max {
let mut least = u64::MAX;
let default_key = Pubkey::default();
let mut least_key = &default_key;

for (key, entry) in self.executors.iter() {
let mut counts = self
.executors
.iter()
.map(|(key, entry)| {
let count = entry.prev_epoch_count + entry.epoch_count.load(Relaxed);
if count < least {
least = count;
least_key = key;
}
(key, count)
})
.collect::<Vec<_>>();
counts.sort_unstable_by_key(|(_, count)| *count);

let primer_count = Self::get_primer_count(counts.as_slice());

if self.executors.len() >= self.max {
if let Some(least_key) = counts.first().map(|least| *least.0) {
let _ = self.executors.remove(&least_key);
self.stats
.evictions
.entry(least_key)
.and_modify(|c| saturating_add_assign!(*c, 1))
.or_insert(1);
}
let least_key = *least_key;
let _ = self.executors.remove(&least_key);
self.stats
.evictions
.entry(least_key)
.and_modify(|c| saturating_add_assign!(*c, 1))
.or_insert(1);
}

CachedExecutorsEntry {
prev_epoch_count: 0,
epoch_count: AtomicU64::new(0),
epoch_count: AtomicU64::new(primer_count),
executor,
}
};
Expand All @@ -481,6 +485,38 @@ impl CachedExecutors {
fn remove(&mut self, pubkey: &Pubkey) {
let _ = self.executors.remove(pubkey);
}

fn get_primer_count_upper_bound_inclusive(counts: &[(&Pubkey, u64)]) -> u64 {
const PRIMER_COUNT_TARGET_PERCENTILE: u64 = 85;
#[allow(clippy::assertions_on_constants)]
{
assert!(PRIMER_COUNT_TARGET_PERCENTILE <= 100);
}
// Executor use-frequencies are assumed to fit a Pareto distribution. Choose an
// upper-bound for our primer count as the actual count at the target rank to avoid
// an upward bias

let target_index = u64::try_from(counts.len().saturating_sub(1))
.ok()
.and_then(|counts| {
let index = counts
.saturating_mul(PRIMER_COUNT_TARGET_PERCENTILE)
.div(100); // switch to u64::saturating_div once stable
usize::try_from(index).ok()
})
.unwrap_or(0);

counts
.get(target_index)
.map(|(_, count)| *count)
.unwrap_or(0)
}

fn get_primer_count(counts: &[(&Pubkey, u64)]) -> u64 {
let max_primer_count = Self::get_primer_count_upper_bound_inclusive(counts);
let mut rng = rand::thread_rng();
rng.gen_range(0, max_primer_count.saturating_add(1))
}
}

pub struct TxComputeMeter {
Expand Down Expand Up @@ -11899,19 +11935,25 @@ pub(crate) mod tests {
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key2).is_some());
cache.put(&key4, executor.clone());
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key2).is_some());
assert!(cache.get(&key3).is_none());
assert!(cache.get(&key4).is_some());
let num_retained = [&key1, &key2, &key3]
.iter()
.map(|key| cache.get(key))
.flatten()
.count();
assert_eq!(num_retained, 2);

assert!(cache.get(&key4).is_some());
assert!(cache.get(&key4).is_some());
assert!(cache.get(&key4).is_some());
cache.put(&key3, executor.clone());
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key2).is_none());
assert!(cache.get(&key3).is_some());
assert!(cache.get(&key4).is_some());
let num_retained = [&key1, &key2, &key4]
.iter()
.map(|key| cache.get(key))
.flatten()
.count();
assert_eq!(num_retained, 2);
}

#[test]
Expand Down Expand Up @@ -11940,12 +11982,23 @@ pub(crate) mod tests {
Arc::make_mut(&mut cache).put(&key4, executor.clone());

assert!(cache.get(&key4).is_some());
assert!(cache.get(&key3).is_none());
let num_retained = [&key1, &key2, &key3]
.iter()
.map(|key| cache.get(key))
.flatten()
.count();
assert_eq!(num_retained, 2);

Arc::make_mut(&mut cache).put(&key1, executor.clone());
Arc::make_mut(&mut cache).put(&key3, executor.clone());
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key4).is_none());
assert!(cache.get(&key3).is_some());
let num_retained = [&key2, &key4]
.iter()
.map(|key| cache.get(key))
.flatten()
.count();
assert_eq!(num_retained, 1);

cache = cache.clone_with_epoch(2);
assert!(cache.current_epoch == 2);
Expand Down Expand Up @@ -14173,4 +14226,71 @@ pub(crate) mod tests {
Some(Vec::<TransactionLogInfo>::new()),
);
}
<<<<<<< HEAD
=======

/// Test exceeding the accounts data budget by creating accounts in a loop
#[test]
fn test_accounts_data_budget_exceeded() {
use solana_program_runtime::accounts_data_meter::MAX_ACCOUNTS_DATA_LEN;
use solana_sdk::system_instruction::MAX_PERMITTED_DATA_LENGTH;

solana_logger::setup();
let (genesis_config, mint_keypair) = create_genesis_config(1_000_000_000_000);
let mut bank = Bank::new_for_tests(&genesis_config);
bank.activate_feature(&solana_sdk::feature_set::cap_accounts_data_len::id());

let mut i = 0;
let result = loop {
let txn = system_transaction::create_account(
&mint_keypair,
&Keypair::new(),
bank.last_blockhash(),
1,
MAX_PERMITTED_DATA_LENGTH,
&solana_sdk::system_program::id(),
);

let result = bank.process_transaction(&txn);
assert!(bank.load_accounts_data_len() <= MAX_ACCOUNTS_DATA_LEN);
if result.is_err() {
break result;
}

assert!(
i < MAX_ACCOUNTS_DATA_LEN / MAX_PERMITTED_DATA_LENGTH,
"test must complete within bounded limits"
);
i += 1;
};

assert!(matches!(
result,
Err(TransactionError::InstructionError(
_,
solana_sdk::instruction::InstructionError::AccountsDataBudgetExceeded,
))
));
}

#[test]
fn test_executor_cache_get_primer_count_upper_bound_inclusive() {
let pubkey = Pubkey::default();
let v = [];
assert_eq!(
CachedExecutors::get_primer_count_upper_bound_inclusive(&v),
0
);
let v = [(&pubkey, 1)];
assert_eq!(
CachedExecutors::get_primer_count_upper_bound_inclusive(&v),
1
);
let v = (0u64..10).map(|i| (&pubkey, i)).collect::<Vec<_>>();
assert_eq!(
CachedExecutors::get_primer_count_upper_bound_inclusive(v.as_slice()),
7
);
}
>>>>>>> 4ce48307b (bank: prime new executor cache entry use-counts)
}

0 comments on commit 4d085d6

Please sign in to comment.