uses structural sharing for stake-delegations hash-map (#23585)

StakeDelegations is using Arc to implement copy-on-write semantics:
https://github.com/solana-labs/solana/blob/58c0db970/runtime/src/stake_delegations.rs#L14-L16

However a single delegation change will still clone the entire hash-map,
resulting in excessive memory use as observed in:
https://github.com/solana-labs/solana/issues/23061#issuecomment-1063444072

This commit instead uses immutable hash-map implementing structural
sharing:
> which means that if two data structures are mostly copies of each
> other, most of the memory they take up will be shared between them.
https://docs.rs/im/latest/im/
This commit is contained in:
behzad nouri
2022-03-16 12:58:05 +00:00
committed by GitHub
parent 584ac80b1e
commit 3252dc7203
10 changed files with 118 additions and 145 deletions

View File

@ -22,6 +22,7 @@ dashmap = { version = "4.0.2", features = ["rayon", "raw-api"] }
dir-diff = "0.3.2"
flate2 = "1.0.22"
fnv = "1.0.7"
im = { version = "15.0.0", features = ["rayon", "serde"] }
index_list = "0.2.7"
itertools = "0.10.3"
lazy_static = "1.4.0"

View File

@ -2545,10 +2545,10 @@ impl Bank {
let invalid_stake_keys: DashMap<Pubkey, InvalidCacheEntryReason> = DashMap::new();
let invalid_vote_keys: DashMap<Pubkey, InvalidCacheEntryReason> = DashMap::new();
let stake_delegations: Vec<_> = stakes.stake_delegations().iter().collect();
thread_pool.install(|| {
stakes
.stake_delegations()
.par_iter()
stake_delegations
.into_par_iter()
.for_each(|(stake_pubkey, delegation)| {
let vote_pubkey = &delegation.voter_pubkey;
if invalid_vote_keys.contains_key(vote_pubkey) {
@ -6729,7 +6729,6 @@ pub(crate) mod tests {
genesis_sysvar_and_builtin_program_lamports, GenesisConfigInfo,
ValidatorVoteKeypairs,
},
stake_delegations::StakeDelegations,
status_cache::MAX_CACHE_ENTRIES,
},
crossbeam_channel::{bounded, unbounded},
@ -6771,12 +6770,6 @@ pub(crate) mod tests {
std::{result, thread::Builder, time::Duration},
};
impl Bank {
fn cloned_stake_delegations(&self) -> StakeDelegations {
self.stakes_cache.stakes().stake_delegations().clone()
}
}
fn new_sanitized_message(
instructions: &[Instruction],
payer: Option<&Pubkey>,
@ -10896,7 +10889,7 @@ pub(crate) mod tests {
} = create_genesis_config_with_leader(500, &solana_sdk::pubkey::new_rand(), 1);
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
let stake_delegations = bank.cloned_stake_delegations();
let stake_delegations = bank.stakes_cache.stakes().stake_delegations().clone();
assert_eq!(stake_delegations.len(), 1); // bootstrap validator has
// to have a stake delegation
@ -10932,7 +10925,7 @@ pub(crate) mod tests {
bank.process_transaction(&transaction).unwrap();
let stake_delegations = bank.cloned_stake_delegations();
let stake_delegations = bank.stakes_cache.stakes().stake_delegations().clone();
assert_eq!(stake_delegations.len(), 2);
assert!(stake_delegations.get(&stake_keypair.pubkey()).is_some());
}

View File

@ -52,7 +52,6 @@ pub mod snapshot_hash;
pub mod snapshot_package;
pub mod snapshot_utils;
pub mod sorted_storages;
pub mod stake_delegations;
pub mod stake_history;
pub mod stake_weighted_timestamp;
pub mod stakes;

View File

@ -305,7 +305,7 @@ mod test_bank_serialize {
// This some what long test harness is required to freeze the ABI of
// Bank's serialization due to versioned nature
#[frozen_abi(digest = "HVyzePMkma8T54PymRW32FAgDXpSdom59K6RnPsCNJjj")]
#[frozen_abi(digest = "4TqK4bCbL76s9mf1gbcRVSQUgtSe68wDCk3jTwoQzr6R")]
#[derive(Serialize, AbiExample)]
pub struct BankAbiTestWrapperNewer {
#[serde(serialize_with = "wrapper_newer")]

View File

@ -1,128 +0,0 @@
//! Map pubkeys to stake delegations
//!
//! This module implements clone-on-write semantics for `StakeDelegations` to reduce unnecessary
//! cloning of the underlying map.
use {
solana_sdk::{pubkey::Pubkey, stake::state::Delegation},
std::{
collections::HashMap,
ops::{Deref, DerefMut},
sync::Arc,
},
};
/// A map of pubkey-to-stake-delegation with clone-on-write semantics
#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize, AbiExample)]
pub struct StakeDelegations(Arc<StakeDelegationsInner>);
impl Deref for StakeDelegations {
type Target = StakeDelegationsInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for StakeDelegations {
fn deref_mut(&mut self) -> &mut Self::Target {
Arc::make_mut(&mut self.0)
}
}
/// The inner type, which maps pubkeys to stake delegations
type StakeDelegationsInner = HashMap<Pubkey, Delegation>;
#[cfg(test)]
mod tests {
use super::*;
/// Ensure that StakeDelegations is indeed clone-on-write
#[test]
fn test_stake_delegations_is_cow() {
let voter_pubkey = Pubkey::new_unique();
let stake = rand::random();
let activation_epoch = rand::random();
let warmup_cooldown_rate = rand::random();
let delegation =
Delegation::new(&voter_pubkey, stake, activation_epoch, warmup_cooldown_rate);
let pubkey = Pubkey::new_unique();
let mut stake_delegations = StakeDelegations::default();
stake_delegations.insert(pubkey, delegation);
// Test: Clone the stake delegations and **do not modify**. Assert the underlying maps are
// the same instance.
{
let stake_delegations2 = stake_delegations.clone();
assert_eq!(stake_delegations, stake_delegations2);
assert!(
Arc::ptr_eq(&stake_delegations.0, &stake_delegations2.0),
"Inner Arc must point to the same HashMap"
);
assert!(
std::ptr::eq(stake_delegations.deref(), stake_delegations2.deref()),
"Deref must point to the same HashMap"
);
}
// Test: Clone the stake delegations and then modify (remove the K-V, then re-add the same
// one, so the stake delegations are still logically equal). Assert the underlying maps
// are unique instances.
{
let mut stake_delegations2 = stake_delegations.clone();
stake_delegations2.clear();
assert_ne!(stake_delegations, stake_delegations2);
stake_delegations2.insert(pubkey, delegation);
assert_eq!(stake_delegations, stake_delegations2);
assert!(
!Arc::ptr_eq(&stake_delegations.0, &stake_delegations2.0),
"Inner Arc must point to different HashMaps"
);
assert!(
!std::ptr::eq(stake_delegations.deref(), stake_delegations2.deref()),
"Deref must point to different HashMaps"
);
}
}
/// Ensure that StakeDelegations serializes and deserializes between the inner and outer types
#[test]
fn test_stake_delegations_serde() {
let voter_pubkey = Pubkey::new_unique();
let stake = rand::random();
let activation_epoch = rand::random();
let warmup_cooldown_rate = rand::random();
let delegation =
Delegation::new(&voter_pubkey, stake, activation_epoch, warmup_cooldown_rate);
let pubkey = Pubkey::new_unique();
let mut stake_delegations_outer = StakeDelegations::default();
stake_delegations_outer.insert(pubkey, delegation);
let mut stake_delegations_inner = StakeDelegationsInner::default();
stake_delegations_inner.insert(pubkey, delegation);
// Test: Assert that serializing the outer and inner types produces the same data
assert_eq!(
bincode::serialize(&stake_delegations_outer).unwrap(),
bincode::serialize(&stake_delegations_inner).unwrap(),
);
// Test: Assert that serializing the outer type then deserializing to the inner type
// produces the same values
{
let data = bincode::serialize(&stake_delegations_outer).unwrap();
let deserialized_inner: StakeDelegationsInner = bincode::deserialize(&data).unwrap();
assert_eq!(&deserialized_inner, stake_delegations_outer.deref());
}
// Test: Assert that serializing the inner type then deserializing to the outer type
// produces the same values
{
let data = bincode::serialize(&stake_delegations_inner).unwrap();
let deserialized_outer: StakeDelegations = bincode::deserialize(&data).unwrap();
assert_eq!(deserialized_outer.deref(), &stake_delegations_inner);
}
}
}

View File

@ -2,11 +2,11 @@
//! node stakes
use {
crate::{
stake_delegations::StakeDelegations,
stake_history::StakeHistory,
vote_account::{VoteAccount, VoteAccounts, VoteAccountsHashMap},
},
dashmap::DashMap,
im::HashMap as ImHashMap,
num_derive::ToPrimitive,
num_traits::ToPrimitive,
rayon::{
@ -151,7 +151,7 @@ pub struct Stakes {
vote_accounts: VoteAccounts,
/// stake_delegations
stake_delegations: StakeDelegations,
stake_delegations: ImHashMap<Pubkey, Delegation>,
/// unused
unused: u64,
@ -337,7 +337,7 @@ impl Stakes {
&self.vote_accounts
}
pub fn stake_delegations(&self) -> &StakeDelegations {
pub(crate) fn stake_delegations(&self) -> &ImHashMap<Pubkey, Delegation> {
&self.stake_delegations
}