Faster kill_garbage (#11514)

* Faster kill_garbage

Benchmark shows that almost half the time `apply()`-ing a transaction is spent in garbage collection. This PR avoids visiting each cache item and `collect()`-ing accounts to clean up.

* Walk back panicking behaviour

* Review grumble: prefer `and_then` to `if let`
This commit is contained in:
David 2020-02-27 13:56:48 +01:00 committed by GitHub
parent 11abf3ea2e
commit 8572d612a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 37 additions and 27 deletions

View File

@ -352,7 +352,7 @@ impl<B: Backend> State<B> {
fn insert_cache(&self, address: &Address, account: AccountEntry) { fn insert_cache(&self, address: &Address, account: AccountEntry) {
// Dirty account which is not in the cache means this is a new account. // Dirty account which is not in the cache means this is a new account.
// It goes directly into the checkpoint as there's nothing to rever to. // It goes directly into the checkpoint as there's nothing to revert to.
// //
// In all other cases account is read as clean first, and after that made // In all other cases account is read as clean first, and after that made
// dirty in and added to the checkpoint with `note_cache`. // dirty in and added to the checkpoint with `note_cache`.
@ -759,20 +759,21 @@ impl<B: Backend> State<B> {
} }
/// Remove any touched empty or dust accounts. /// Remove any touched empty or dust accounts.
pub fn kill_garbage(&mut self, touched: &HashSet<Address>, remove_empty_touched: bool, min_balance: &Option<U256>, kill_contracts: bool) -> TrieResult<()> { pub fn kill_garbage(&mut self, touched: &HashSet<Address>, min_balance: &Option<U256>, kill_contracts: bool) -> TrieResult<()> {
let to_kill: HashSet<_> = { let to_kill: HashSet<_> =
self.cache.borrow().iter().filter_map(|(address, ref entry)| touched.iter().filter_map(|address| { // Check all touched accounts
if touched.contains(address) && // Check all touched accounts self.cache.borrow().get(address).and_then(|entry| {
((remove_empty_touched && entry.exists_and_is_null()) // Remove all empty touched accounts. if entry.exists_and_is_null() // Remove all empty touched accounts.
|| min_balance.map_or(false, |ref balance| entry.account.as_ref().map_or(false, |account| || min_balance.map_or(false, |ref balance| entry.account.as_ref().map_or(false, |account|
(account.is_basic() || kill_contracts) // Remove all basic and optionally contract accounts where balance has been decreased. (account.is_basic() || kill_contracts) // Remove all basic and optionally contract accounts where balance has been decreased.
&& account.balance() < balance && entry.old_balance.as_ref().map_or(false, |b| account.balance() < b)))) { && account.balance() < balance && entry.old_balance.as_ref().map_or(false, |b| account.balance() < b))) {
Some(address)
} else { None }
})
}).collect();
Some(address.clone())
} else { None }).collect()
};
for address in to_kill { for address in to_kill {
self.kill_account(&address); self.kill_account(address)
} }
Ok(()) Ok(())
} }

View File

@ -1573,15 +1573,15 @@ mod tests {
state.transfer_balance(&b, &x, &1.into(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account decreasing its balance state.transfer_balance(&b, &x, &1.into(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account decreasing its balance
state.transfer_balance(&c, &x, &1.into(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account decreasing its balance state.transfer_balance(&c, &x, &1.into(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account decreasing its balance
state.transfer_balance(&e, &x, &1.into(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account decreasing its balance state.transfer_balance(&e, &x, &1.into(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account decreasing its balance
state.kill_garbage(&touched, true, &None, false).unwrap(); state.kill_garbage(&touched, &None, false).unwrap();
assert!(!state.exists(&a).unwrap()); assert!(!state.exists(&a).unwrap());
assert!(state.exists(&b).unwrap()); assert!(state.exists(&b).unwrap());
state.kill_garbage(&touched, true, &Some(100.into()), false).unwrap(); state.kill_garbage(&touched,&Some(100.into()), false).unwrap();
assert!(!state.exists(&b).unwrap()); assert!(!state.exists(&b).unwrap());
assert!(state.exists(&c).unwrap()); assert!(state.exists(&c).unwrap());
assert!(state.exists(&d).unwrap()); assert!(state.exists(&d).unwrap());
assert!(state.exists(&e).unwrap()); assert!(state.exists(&e).unwrap());
state.kill_garbage(&touched, true, &Some(100.into()), true).unwrap(); state.kill_garbage(&touched, &Some(100.into()), true).unwrap();
assert!(state.exists(&c).unwrap()); assert!(state.exists(&c).unwrap());
assert!(state.exists(&d).unwrap()); assert!(state.exists(&d).unwrap());
assert!(!state.exists(&e).unwrap()); assert!(!state.exists(&e).unwrap());

View File

@ -1175,9 +1175,17 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
} }
// perform garbage-collection // perform garbage-collection
let min_balance = if schedule.kill_dust != CleanDustMode::Off { Some(U256::from(schedule.tx_gas).overflowing_mul(t.gas_price).0) } else { None }; if schedule.kill_empty {
self.state.kill_garbage(&substate.touched, schedule.kill_empty, &min_balance, schedule.kill_dust == CleanDustMode::WithCodeAndStorage)?; let (min_balance, kill_contracts) = if schedule.kill_dust != CleanDustMode::Off {
(
Some(U256::from(schedule.tx_gas).overflowing_mul(t.gas_price).0),
schedule.kill_dust == CleanDustMode::WithCodeAndStorage,
)
} else {
(None, false)
};
self.state.kill_garbage(&substate.touched, &min_balance, kill_contracts)?;
}
match result { match result {
Err(vm::Error::Internal(msg)) => Err(ExecutionError::Internal(msg)), Err(vm::Error::Internal(msg)) => Err(ExecutionError::Internal(msg)),
Err(exception) => { Err(exception) => {
@ -1189,9 +1197,9 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
cumulative_gas_used: self.info.gas_used + t.gas, cumulative_gas_used: self.info.gas_used + t.gas,
logs: vec![], logs: vec![],
contracts_created: vec![], contracts_created: vec![],
output: output, output,
trace: trace, trace,
vm_trace: vm_trace, vm_trace,
state_diff: None, state_diff: None,
}) })
}, },

View File

@ -286,12 +286,13 @@ impl<'a> EvmTestClient<'a> {
}).ok(); }).ok();
// Touching also means that we should remove the account if it's within eip161 // Touching also means that we should remove the account if it's within eip161
// conditions. // conditions.
self.state.kill_garbage( if schedule.kill_empty {
&vec![env_info.author].into_iter().collect(), self.state.kill_garbage(
schedule.kill_empty, &vec![env_info.author].into_iter().collect(),
&None, &None,
false false
).ok(); ).ok();
}
self.state.commit().ok(); self.state.commit().ok();