Avoid RMWs on shared data inside parallel loops: collect_rent_from_accounts() (#25790)
This commit is contained in:
parent
67a11ce4b1
commit
ec64d5261f
|
@ -80,7 +80,7 @@ use {
|
||||||
iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator},
|
iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator},
|
||||||
ThreadPool, ThreadPoolBuilder,
|
ThreadPool, ThreadPoolBuilder,
|
||||||
},
|
},
|
||||||
solana_measure::measure::Measure,
|
solana_measure::{measure, measure::Measure},
|
||||||
solana_metrics::{inc_new_counter_debug, inc_new_counter_info},
|
solana_metrics::{inc_new_counter_debug, inc_new_counter_info},
|
||||||
solana_program_runtime::{
|
solana_program_runtime::{
|
||||||
accounts_data_meter::MAX_ACCOUNTS_DATA_LEN,
|
accounts_data_meter::MAX_ACCOUNTS_DATA_LEN,
|
||||||
|
@ -5240,31 +5240,39 @@ impl Bank {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collect rent from `accounts`
|
||||||
|
///
|
||||||
|
/// This fn is called inside a parallel loop from `collect_rent_in_partition()`. Avoid adding
|
||||||
|
/// any code that causes contention on shared memory/data (i.e. do not update atomic metrics).
|
||||||
|
///
|
||||||
|
/// The return value is a struct of computed values that `collect_rent_in_partition()` will
|
||||||
|
/// reduce at the end of its parallel loop. If possible, place data/computation that cause
|
||||||
|
/// contention/take locks in the return struct and process them in
|
||||||
|
/// `collect_rent_from_partition()` after reducing the parallel loop.
|
||||||
fn collect_rent_from_accounts(
|
fn collect_rent_from_accounts(
|
||||||
&self,
|
&self,
|
||||||
mut accounts: Vec<(Pubkey, AccountSharedData, Slot)>,
|
mut accounts: Vec<(Pubkey, AccountSharedData, Slot)>,
|
||||||
metrics: &RentMetrics,
|
|
||||||
just_rewrites: bool,
|
just_rewrites: bool,
|
||||||
) {
|
) -> CollectRentFromAccountsInfo {
|
||||||
let mut rent_debits = RentDebits::default();
|
let mut rent_debits = RentDebits::default();
|
||||||
let mut total_collected = CollectedInfo::default();
|
let mut total_rent_collected_info = CollectedInfo::default();
|
||||||
let bank_slot = self.slot();
|
let bank_slot = self.slot();
|
||||||
let mut rewrites_skipped = Vec::with_capacity(accounts.len());
|
let mut rewrites_skipped = Vec::with_capacity(accounts.len());
|
||||||
let mut accounts_to_store =
|
let mut accounts_to_store =
|
||||||
Vec::<(&Pubkey, &AccountSharedData)>::with_capacity(accounts.len());
|
Vec::<(&Pubkey, &AccountSharedData)>::with_capacity(accounts.len());
|
||||||
let mut collect_us = 0;
|
let mut time_collecting_rent_us = 0;
|
||||||
let mut hash_skipped_rewrites_us = 0;
|
let mut time_hashing_skipped_rewrites_us = 0;
|
||||||
|
let mut time_storing_accounts_us = 0;
|
||||||
let can_skip_rewrites = self.rc.accounts.accounts_db.skip_rewrites || just_rewrites;
|
let can_skip_rewrites = self.rc.accounts.accounts_db.skip_rewrites || just_rewrites;
|
||||||
for (pubkey, account, loaded_slot) in accounts.iter_mut() {
|
for (pubkey, account, loaded_slot) in accounts.iter_mut() {
|
||||||
let old_rent_epoch = account.rent_epoch();
|
let (rent_collected_info, measure) =
|
||||||
let mut time = Measure::start("collect");
|
measure!(self.rent_collector.collect_from_existing_account(
|
||||||
let collected = self.rent_collector.collect_from_existing_account(
|
pubkey,
|
||||||
pubkey,
|
account,
|
||||||
account,
|
self.rc.accounts.accounts_db.filler_account_suffix.as_ref(),
|
||||||
self.rc.accounts.accounts_db.filler_account_suffix.as_ref(),
|
));
|
||||||
);
|
time_collecting_rent_us += measure.as_us();
|
||||||
time.stop();
|
|
||||||
collect_us += time.as_us();
|
|
||||||
// only store accounts where we collected rent
|
// only store accounts where we collected rent
|
||||||
// but get the hash for all these accounts even if collected rent is 0 (= not updated).
|
// but get the hash for all these accounts even if collected rent is 0 (= not updated).
|
||||||
// Also, there's another subtle side-effect from this: this
|
// Also, there's another subtle side-effect from this: this
|
||||||
|
@ -5273,48 +5281,45 @@ impl Bank {
|
||||||
if can_skip_rewrites
|
if can_skip_rewrites
|
||||||
&& Self::skip_rewrite(
|
&& Self::skip_rewrite(
|
||||||
bank_slot,
|
bank_slot,
|
||||||
collected.rent_amount,
|
rent_collected_info.rent_amount,
|
||||||
*loaded_slot,
|
*loaded_slot,
|
||||||
old_rent_epoch,
|
account.rent_epoch(),
|
||||||
account,
|
account,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// this would have been rewritten previously. Now we skip it.
|
// this would have been rewritten previously. Now we skip it.
|
||||||
// calculate the hash that we would have gotten if we did the rewrite.
|
// calculate the hash that we would have gotten if we did the rewrite.
|
||||||
// This will be needed to calculate the bank's hash.
|
// This will be needed to calculate the bank's hash.
|
||||||
let mut time = Measure::start("hash_account");
|
let (hash, measure) = measure!(crate::accounts_db::AccountsDb::hash_account(
|
||||||
let hash =
|
self.slot(),
|
||||||
crate::accounts_db::AccountsDb::hash_account(self.slot(), account, pubkey);
|
account,
|
||||||
time.stop();
|
pubkey
|
||||||
hash_skipped_rewrites_us += time.as_us();
|
));
|
||||||
|
time_hashing_skipped_rewrites_us += measure.as_us();
|
||||||
rewrites_skipped.push((*pubkey, hash));
|
rewrites_skipped.push((*pubkey, hash));
|
||||||
assert_eq!(collected, CollectedInfo::default());
|
assert_eq!(rent_collected_info, CollectedInfo::default());
|
||||||
} else if !just_rewrites {
|
} else if !just_rewrites {
|
||||||
total_collected += collected;
|
total_rent_collected_info += rent_collected_info;
|
||||||
accounts_to_store.push((pubkey, account));
|
accounts_to_store.push((pubkey, account));
|
||||||
}
|
}
|
||||||
rent_debits.insert(pubkey, collected.rent_amount, account.lamports());
|
rent_debits.insert(pubkey, rent_collected_info.rent_amount, account.lamports());
|
||||||
}
|
}
|
||||||
metrics.collect_us.fetch_add(collect_us, Relaxed);
|
|
||||||
metrics.hash_us.fetch_add(hash_skipped_rewrites_us, Relaxed);
|
|
||||||
|
|
||||||
if !accounts_to_store.is_empty() {
|
if !accounts_to_store.is_empty() {
|
||||||
let mut time = Measure::start("store_account");
|
// TODO: Maybe do not call `store_accounts()` here. Instead return `accounts_to_store`
|
||||||
self.store_accounts(&accounts_to_store);
|
// and have `collect_rent_in_partition()` perform all the stores.
|
||||||
time.stop();
|
let (_, measure) = measure!(self.store_accounts(&accounts_to_store));
|
||||||
metrics.store_us.fetch_add(time.as_us(), Relaxed);
|
time_storing_accounts_us += measure.as_us();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.remember_skipped_rewrites(rewrites_skipped);
|
CollectRentFromAccountsInfo {
|
||||||
self.collected_rent
|
rent_collected_info: total_rent_collected_info,
|
||||||
.fetch_add(total_collected.rent_amount, Relaxed);
|
rent_rewards: rent_debits.into_unordered_rewards_iter().collect(),
|
||||||
self.rewards
|
rewrites_skipped,
|
||||||
.write()
|
time_collecting_rent_us,
|
||||||
.unwrap()
|
time_hashing_skipped_rewrites_us,
|
||||||
.extend(rent_debits.into_unordered_rewards_iter());
|
time_storing_accounts_us,
|
||||||
self.update_accounts_data_size_delta_off_chain(
|
}
|
||||||
-(total_collected.account_data_len_reclaimed as i64),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// load accounts with pubkeys in 'partition'
|
/// load accounts with pubkeys in 'partition'
|
||||||
|
@ -5348,10 +5353,9 @@ impl Bank {
|
||||||
let end_prefix_inclusive = Self::prefix_from_pubkey(subrange_full.end());
|
let end_prefix_inclusive = Self::prefix_from_pubkey(subrange_full.end());
|
||||||
let range = end_prefix_inclusive - start_prefix;
|
let range = end_prefix_inclusive - start_prefix;
|
||||||
let increment = range / num_threads;
|
let increment = range / num_threads;
|
||||||
for thread_metrics in (0..num_threads)
|
let mut results = (0..num_threads)
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|chunk| {
|
.map(|chunk| {
|
||||||
let metrics = RentMetrics::default();
|
|
||||||
let offset = |chunk| start_prefix + chunk * increment;
|
let offset = |chunk| start_prefix + chunk * increment;
|
||||||
let start = offset(chunk);
|
let start = offset(chunk);
|
||||||
let last = chunk == num_threads - 1;
|
let last = chunk == num_threads - 1;
|
||||||
|
@ -5360,8 +5364,7 @@ impl Bank {
|
||||||
bound
|
bound
|
||||||
};
|
};
|
||||||
let start = merge_prefix(start, *subrange_full.start());
|
let start = merge_prefix(start, *subrange_full.start());
|
||||||
let mut load = Measure::start("load");
|
let (accounts, measure_load_accounts) = measure!(if last {
|
||||||
let accounts = if last {
|
|
||||||
let end = *subrange_full.end();
|
let end = *subrange_full.end();
|
||||||
let subrange = start..=end; // IN-clusive
|
let subrange = start..=end; // IN-clusive
|
||||||
self.rc
|
self.rc
|
||||||
|
@ -5373,32 +5376,44 @@ impl Bank {
|
||||||
self.rc
|
self.rc
|
||||||
.accounts
|
.accounts
|
||||||
.load_to_collect_rent_eagerly(&self.ancestors, subrange)
|
.load_to_collect_rent_eagerly(&self.ancestors, subrange)
|
||||||
};
|
});
|
||||||
load.stop();
|
CollectRentInPartitionInfo::new(
|
||||||
metrics.load_us.fetch_add(load.as_us(), Relaxed);
|
self.collect_rent_from_accounts(accounts, just_rewrites),
|
||||||
|
Duration::from_nanos(measure_load_accounts.as_ns()),
|
||||||
self.collect_rent_from_accounts(accounts, &metrics, just_rewrites);
|
)
|
||||||
metrics
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.reduce(
|
||||||
{
|
CollectRentInPartitionInfo::default,
|
||||||
metrics
|
CollectRentInPartitionInfo::reduce,
|
||||||
.load_us
|
);
|
||||||
.fetch_add(thread_metrics.load_us.load(Relaxed), Relaxed);
|
|
||||||
metrics
|
|
||||||
.store_us
|
|
||||||
.fetch_add(thread_metrics.store_us.load(Relaxed), Relaxed);
|
|
||||||
metrics
|
|
||||||
.hash_us
|
|
||||||
.fetch_add(thread_metrics.hash_us.load(Relaxed), Relaxed);
|
|
||||||
metrics
|
|
||||||
.collect_us
|
|
||||||
.fetch_add(thread_metrics.collect_us.load(Relaxed), Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.rc
|
self.rc
|
||||||
.accounts
|
.accounts
|
||||||
.hold_range_in_memory(&subrange_full, false, thread_pool);
|
.hold_range_in_memory(&subrange_full, false, thread_pool);
|
||||||
|
|
||||||
|
self.collected_rent
|
||||||
|
.fetch_add(results.rent_collected, Relaxed);
|
||||||
|
self.update_accounts_data_size_delta_off_chain(
|
||||||
|
-(results.accounts_data_size_reclaimed as i64),
|
||||||
|
);
|
||||||
|
self.rewards
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.append(&mut results.rent_rewards);
|
||||||
|
self.remember_skipped_rewrites(results.rewrites_skipped);
|
||||||
|
|
||||||
|
metrics
|
||||||
|
.load_us
|
||||||
|
.fetch_add(results.time_loading_accounts_us, Relaxed);
|
||||||
|
metrics
|
||||||
|
.collect_us
|
||||||
|
.fetch_add(results.time_collecting_rent_us, Relaxed);
|
||||||
|
metrics
|
||||||
|
.hash_us
|
||||||
|
.fetch_add(results.time_hashing_skipped_rewrites_us, Relaxed);
|
||||||
|
metrics
|
||||||
|
.store_us
|
||||||
|
.fetch_add(results.time_storing_accounts_us, Relaxed);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7479,6 +7494,81 @@ impl Bank {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the computed values from `collect_rent_from_accounts()`
|
||||||
|
///
|
||||||
|
/// Since `collect_rent_from_accounts()` is running in parallel, instead of updating the
|
||||||
|
/// atomics/shared data inside this function, return those values in this struct for the caller to
|
||||||
|
/// process later.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct CollectRentFromAccountsInfo {
|
||||||
|
rent_collected_info: CollectedInfo,
|
||||||
|
rent_rewards: Vec<(Pubkey, RewardInfo)>,
|
||||||
|
rewrites_skipped: Vec<(Pubkey, Hash)>,
|
||||||
|
time_collecting_rent_us: u64,
|
||||||
|
time_hashing_skipped_rewrites_us: u64,
|
||||||
|
time_storing_accounts_us: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the computed values—of each iteration in the parallel loop inside
|
||||||
|
/// `collect_rent_in_partition()`—and then perform a reduce on all of them.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct CollectRentInPartitionInfo {
|
||||||
|
rent_collected: u64,
|
||||||
|
accounts_data_size_reclaimed: u64,
|
||||||
|
rent_rewards: Vec<(Pubkey, RewardInfo)>,
|
||||||
|
rewrites_skipped: Vec<(Pubkey, Hash)>,
|
||||||
|
time_loading_accounts_us: u64,
|
||||||
|
time_collecting_rent_us: u64,
|
||||||
|
time_hashing_skipped_rewrites_us: u64,
|
||||||
|
time_storing_accounts_us: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CollectRentInPartitionInfo {
|
||||||
|
/// Create a new `CollectRentInPartitionInfo` from the results of loading accounts and
|
||||||
|
/// collecting rent on them.
|
||||||
|
#[must_use]
|
||||||
|
fn new(info: CollectRentFromAccountsInfo, time_loading_accounts: Duration) -> Self {
|
||||||
|
Self {
|
||||||
|
rent_collected: info.rent_collected_info.rent_amount,
|
||||||
|
accounts_data_size_reclaimed: info.rent_collected_info.account_data_len_reclaimed,
|
||||||
|
rent_rewards: info.rent_rewards,
|
||||||
|
rewrites_skipped: info.rewrites_skipped,
|
||||||
|
time_loading_accounts_us: time_loading_accounts.as_micros() as u64,
|
||||||
|
time_collecting_rent_us: info.time_collecting_rent_us,
|
||||||
|
time_hashing_skipped_rewrites_us: info.time_hashing_skipped_rewrites_us,
|
||||||
|
time_storing_accounts_us: info.time_storing_accounts_us,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reduce (i.e. 'combine') two `CollectRentInPartitionInfo`s into one.
|
||||||
|
///
|
||||||
|
/// This fn is used by `collect_rent_in_partition()` as the reduce step (of map-reduce) in its
|
||||||
|
/// parallel loop of rent collection.
|
||||||
|
#[must_use]
|
||||||
|
fn reduce(lhs: Self, rhs: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
rent_collected: lhs.rent_collected.saturating_add(rhs.rent_collected),
|
||||||
|
accounts_data_size_reclaimed: lhs
|
||||||
|
.accounts_data_size_reclaimed
|
||||||
|
.saturating_add(rhs.accounts_data_size_reclaimed),
|
||||||
|
rent_rewards: [lhs.rent_rewards, rhs.rent_rewards].concat(),
|
||||||
|
rewrites_skipped: [lhs.rewrites_skipped, rhs.rewrites_skipped].concat(),
|
||||||
|
time_loading_accounts_us: lhs
|
||||||
|
.time_loading_accounts_us
|
||||||
|
.saturating_add(rhs.time_loading_accounts_us),
|
||||||
|
time_collecting_rent_us: lhs
|
||||||
|
.time_collecting_rent_us
|
||||||
|
.saturating_add(rhs.time_collecting_rent_us),
|
||||||
|
time_hashing_skipped_rewrites_us: lhs
|
||||||
|
.time_hashing_skipped_rewrites_us
|
||||||
|
.saturating_add(rhs.time_hashing_skipped_rewrites_us),
|
||||||
|
time_storing_accounts_us: lhs
|
||||||
|
.time_storing_accounts_us
|
||||||
|
.saturating_add(rhs.time_storing_accounts_us),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Struct to collect stats when scanning all accounts in `get_total_accounts_stats()`
|
/// Struct to collect stats when scanning all accounts in `get_total_accounts_stats()`
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct TotalAccountsStats {
|
pub struct TotalAccountsStats {
|
||||||
|
|
Loading…
Reference in New Issue