From 966b6999d18c609162f2d5b4a25d8e1c78946c92 Mon Sep 17 00:00:00 2001 From: sakridge Date: Mon, 10 Jun 2019 18:15:39 -0700 Subject: [PATCH] Accounts index opt (#4621) * Add accounts_index bench * Don't take the accounts index lock unless needed * Accounts_index remove insert return vec and add capacity stats * Use hashbrown hashmap for accounts_index --- runtime/benches/accounts_index.rs | 46 +++++++++++++++ runtime/src/accounts_db.rs | 37 ++++++++---- runtime/src/accounts_index.rs | 95 +++++++++++++++++++------------ runtime/src/lib.rs | 2 +- 4 files changed, 131 insertions(+), 49 deletions(-) create mode 100644 runtime/benches/accounts_index.rs diff --git a/runtime/benches/accounts_index.rs b/runtime/benches/accounts_index.rs new file mode 100644 index 0000000000..88b5cbf230 --- /dev/null +++ b/runtime/benches/accounts_index.rs @@ -0,0 +1,46 @@ +#![feature(test)] + +extern crate test; + +use rand::{thread_rng, Rng}; +use solana_runtime::accounts_db::AccountInfo; +use solana_runtime::accounts_index::AccountsIndex; +use solana_sdk::pubkey::Pubkey; +use test::Bencher; + +#[bench] +fn bench_accounts_index(bencher: &mut Bencher) { + const NUM_PUBKEYS: usize = 10_000; + let pubkeys: Vec<_> = (0..NUM_PUBKEYS) + .into_iter() + .map(|_| Pubkey::new_rand()) + .collect(); + + const NUM_FORKS: u64 = 16; + + let mut reclaims = vec![]; + let mut index = AccountsIndex::::default(); + for f in 0..NUM_FORKS { + for _p in 0..NUM_PUBKEYS { + index.insert(f, &pubkeys[_p], AccountInfo::default(), &mut reclaims); + } + } + + let mut fork = NUM_FORKS; + let mut root = 0; + bencher.iter(|| { + for _p in 0..NUM_PUBKEYS { + let pubkey = thread_rng().gen_range(0, NUM_PUBKEYS); + index.insert( + fork, + &pubkeys[pubkey], + AccountInfo::default(), + &mut reclaims, + ); + reclaims.clear(); + } + index.add_root(root); + root += 1; + fork += 1; + }); +} diff --git a/runtime/src/accounts_db.rs b/runtime/src/accounts_db.rs index cac98e4b1e..cf4cce0655 100644 --- a/runtime/src/accounts_db.rs +++ b/runtime/src/accounts_db.rs @@ -474,14 +474,14 @@ impl AccountsDB { fork_id: Fork, infos: Vec, accounts: &[(&Pubkey, &Account)], - ) -> Vec<(Fork, AccountInfo)> { + ) -> (Vec<(Fork, AccountInfo)>, u64) { + let mut reclaims = Vec::with_capacity(infos.len() * 2); let mut index = self.accounts_index.write().unwrap(); - let mut reclaims = vec![]; for (i, info) in infos.into_iter().enumerate() { let key = &accounts[i].0; - reclaims.extend(index.insert(fork_id, key, info).into_iter()) + index.insert(fork_id, key, info, &mut reclaims); } - reclaims + (reclaims, index.last_root) } fn remove_dead_accounts(&self, reclaims: Vec<(Fork, AccountInfo)>) -> HashSet { @@ -516,24 +516,30 @@ impl AccountsDB { dead_forks } - fn cleanup_dead_forks(&self, dead_forks: &mut HashSet) { - let mut index = self.accounts_index.write().unwrap(); + fn cleanup_dead_forks(&self, dead_forks: &mut HashSet, last_root: u64) { // a fork is not totally dead until it is older than the root - dead_forks.retain(|fork| *fork < index.last_root); - for fork in dead_forks.iter() { - index.cleanup_dead_fork(*fork); + dead_forks.retain(|fork| *fork < last_root); + if !dead_forks.is_empty() { + let mut index = self.accounts_index.write().unwrap(); + for fork in dead_forks.iter() { + index.cleanup_dead_fork(*fork); + } } } /// Store the account update. pub fn store(&self, fork_id: Fork, accounts: &[(&Pubkey, &Account)]) { let infos = self.store_accounts(fork_id, accounts); - let reclaims = self.update_index(fork_id, infos, accounts); + + let (reclaims, last_root) = self.update_index(fork_id, infos, accounts); trace!("reclaim: {}", reclaims.len()); + let mut dead_forks = self.remove_dead_accounts(reclaims); trace!("dead_forks: {}", dead_forks.len()); - self.cleanup_dead_forks(&mut dead_forks); + + self.cleanup_dead_forks(&mut dead_forks, last_root); trace!("purge_forks: {}", dead_forks.len()); + for fork in dead_forks { self.purge_fork(fork); } @@ -1182,7 +1188,14 @@ mod tests { .clone(); //fork 0 is behind root, but it is not root, therefore it is purged accounts.add_root(1); - assert!(accounts.accounts_index.read().unwrap().is_purged(0)); + { + let accounts_index = accounts.accounts_index.read().unwrap(); + assert!(AccountsIndex::::is_purged( + &accounts_index.roots, + accounts_index.last_root, + 0 + )); + } //fork is still there, since gc is lazy assert!(accounts.storage.read().unwrap().0[&0] diff --git a/runtime/src/accounts_index.rs b/runtime/src/accounts_index.rs index b09846e885..7069fdbcac 100644 --- a/runtime/src/accounts_index.rs +++ b/runtime/src/accounts_index.rs @@ -1,7 +1,9 @@ +use hashbrown::HashMap; use log::*; use serde::{Deserialize, Serialize}; use solana_sdk::pubkey::Pubkey; -use std::collections::{HashMap, HashSet}; +use std::collections; +use std::collections::HashSet; pub type Fork = u64; @@ -19,7 +21,11 @@ pub struct AccountsIndex { impl AccountsIndex { /// Get an account /// The latest account that appears in `ancestors` or `roots` is returned. - pub fn get(&self, pubkey: &Pubkey, ancestors: &HashMap) -> Option<(&T, Fork)> { + pub fn get( + &self, + pubkey: &Pubkey, + ancestors: &collections::HashMap, + ) -> Option<(&T, Fork)> { let list = self.account_maps.get(pubkey)?; let mut max = 0; let mut rv = None; @@ -33,35 +39,34 @@ impl AccountsIndex { rv } - /// Insert a new fork. - /// @retval - The return value contains any squashed accounts that can freed from storage. - pub fn insert(&mut self, fork: Fork, pubkey: &Pubkey, account_info: T) -> Vec<(Fork, T)> { - let mut rv = vec![]; - let mut fork_vec: Vec<(Fork, T)> = vec![]; - { - let entry = self.account_maps.entry(*pubkey).or_insert_with(|| vec![]); - std::mem::swap(entry, &mut fork_vec); - }; + pub fn insert( + &mut self, + fork: Fork, + pubkey: &Pubkey, + account_info: T, + reclaims: &mut Vec<(Fork, T)>, + ) { + let last_root = self.last_root; + let roots = &self.roots; + let fork_vec = self + .account_maps + .entry(*pubkey) + .or_insert_with(|| (Vec::with_capacity(32))); // filter out old entries - rv.extend(fork_vec.iter().filter(|(f, _)| *f == fork).cloned()); + reclaims.extend(fork_vec.iter().filter(|(f, _)| *f == fork).cloned()); fork_vec.retain(|(f, _)| *f != fork); // add the new entry fork_vec.push((fork, account_info)); - rv.extend( + reclaims.extend( fork_vec .iter() - .filter(|(fork, _)| self.is_purged(*fork)) + .filter(|(fork, _)| Self::is_purged(roots, last_root, *fork)) .cloned(), ); - fork_vec.retain(|(fork, _)| !self.is_purged(*fork)); - { - let entry = self.account_maps.entry(*pubkey).or_insert_with(|| vec![]); - std::mem::swap(entry, &mut fork_vec); - }; - rv + fork_vec.retain(|(fork, _)| !Self::is_purged(roots, last_root, *fork)); } pub fn add_index(&mut self, fork: Fork, pubkey: &Pubkey, account_info: T) { @@ -69,12 +74,14 @@ impl AccountsIndex { entry.push((fork, account_info)); } - pub fn is_purged(&self, fork: Fork) -> bool { - !self.is_root(fork) && fork < self.last_root + pub fn is_purged(roots: &HashSet, last_root: Fork, fork: Fork) -> bool { + !roots.contains(&fork) && fork < last_root } + pub fn is_root(&self, fork: Fork) -> bool { self.roots.contains(&fork) } + pub fn add_root(&mut self, fork: Fork) { assert!( (self.last_root == 0 && fork == 0) || (fork > self.last_root), @@ -99,7 +106,7 @@ mod tests { fn test_get_empty() { let key = Keypair::new(); let index = AccountsIndex::::default(); - let ancestors = HashMap::new(); + let ancestors = collections::HashMap::new(); assert_eq!(index.get(&key.pubkey(), &ancestors), None); } @@ -107,10 +114,11 @@ mod tests { fn test_insert_no_ancestors() { let key = Keypair::new(); let mut index = AccountsIndex::::default(); - let gc = index.insert(0, &key.pubkey(), true); + let mut gc = Vec::new(); + index.insert(0, &key.pubkey(), true, &mut gc); assert!(gc.is_empty()); - let ancestors = HashMap::new(); + let ancestors = collections::HashMap::new(); assert_eq!(index.get(&key.pubkey(), &ancestors), None); } @@ -118,7 +126,8 @@ mod tests { fn test_insert_wrong_ancestors() { let key = Keypair::new(); let mut index = AccountsIndex::::default(); - let gc = index.insert(0, &key.pubkey(), true); + let mut gc = Vec::new(); + index.insert(0, &key.pubkey(), true, &mut gc); assert!(gc.is_empty()); let ancestors = vec![(1, 1)].into_iter().collect(); @@ -129,7 +138,8 @@ mod tests { fn test_insert_with_ancestors() { let key = Keypair::new(); let mut index = AccountsIndex::::default(); - let gc = index.insert(0, &key.pubkey(), true); + let mut gc = Vec::new(); + index.insert(0, &key.pubkey(), true, &mut gc); assert!(gc.is_empty()); let ancestors = vec![(0, 0)].into_iter().collect(); @@ -148,7 +158,8 @@ mod tests { fn test_insert_with_root() { let key = Keypair::new(); let mut index = AccountsIndex::::default(); - let gc = index.insert(0, &key.pubkey(), true); + let mut gc = Vec::new(); + index.insert(0, &key.pubkey(), true, &mut gc); assert!(gc.is_empty()); let ancestors = vec![].into_iter().collect(); @@ -159,9 +170,17 @@ mod tests { #[test] fn test_is_purged() { let mut index = AccountsIndex::::default(); - assert!(!index.is_purged(0)); + assert!(!AccountsIndex::::is_purged( + &index.roots, + index.last_root, + 0 + )); index.add_root(1); - assert!(index.is_purged(0)); + assert!(AccountsIndex::::is_purged( + &index.roots, + index.last_root, + 0 + )); } #[test] @@ -205,11 +224,13 @@ mod tests { let key = Keypair::new(); let mut index = AccountsIndex::::default(); let ancestors = vec![(0, 0)].into_iter().collect(); - let gc = index.insert(0, &key.pubkey(), true); + let mut gc = Vec::new(); + index.insert(0, &key.pubkey(), true, &mut gc); assert!(gc.is_empty()); assert_eq!(index.get(&key.pubkey(), &ancestors), Some((&true, 0))); - let gc = index.insert(0, &key.pubkey(), false); + let mut gc = Vec::new(); + index.insert(0, &key.pubkey(), false, &mut gc); assert_eq!(gc, vec![(0, true)]); assert_eq!(index.get(&key.pubkey(), &ancestors), Some((&false, 0))); } @@ -219,9 +240,10 @@ mod tests { let key = Keypair::new(); let mut index = AccountsIndex::::default(); let ancestors = vec![(0, 0)].into_iter().collect(); - let gc = index.insert(0, &key.pubkey(), true); + let mut gc = Vec::new(); + index.insert(0, &key.pubkey(), true, &mut gc); assert!(gc.is_empty()); - let gc = index.insert(1, &key.pubkey(), false); + index.insert(1, &key.pubkey(), false, &mut gc); assert!(gc.is_empty()); assert_eq!(index.get(&key.pubkey(), &ancestors), Some((&true, 0))); let ancestors = vec![(1, 0)].into_iter().collect(); @@ -232,10 +254,11 @@ mod tests { fn test_update_gc_purged_fork() { let key = Keypair::new(); let mut index = AccountsIndex::::default(); - let gc = index.insert(0, &key.pubkey(), true); + let mut gc = Vec::new(); + index.insert(0, &key.pubkey(), true, &mut gc); assert!(gc.is_empty()); index.add_root(1); - let gc = index.insert(1, &key.pubkey(), false); + index.insert(1, &key.pubkey(), false, &mut gc); assert_eq!(gc, vec![(0, true)]); let ancestors = vec![].into_iter().collect(); assert_eq!(index.get(&key.pubkey(), &ancestors), Some((&false, 1))); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 44f7c64ece..6b11c92225 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1,6 +1,6 @@ mod accounts; pub mod accounts_db; -mod accounts_index; +pub mod accounts_index; pub mod append_vec; pub mod bank; pub mod bank_client;