Skip to content

Commit 3b3bd96

Browse files
committed
perf: improve extend_sorted_vec & write batch for HashedPostState
1 parent e57fe45 commit 3b3bd96

File tree

2 files changed

+45
-33
lines changed

2 files changed

+45
-33
lines changed

crates/storage/provider/src/providers/database/provider.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ impl<TX: DbTx + DbTxMut + 'static, N: NodeTypesForProvider> DatabaseProvider<TX,
306306

307307
debug!(target: "providers::db", block_count = %blocks.len(), "Writing blocks and execution data to storage");
308308

309+
let mut aggregated_hashed_post_state_sorted = HashedPostStateSorted::default();
310+
309311
// TODO: Do performant / batched writes for each type of object
310312
// instead of a loop over all blocks,
311313
// meaning:
@@ -325,13 +327,16 @@ impl<TX: DbTx + DbTxMut + 'static, N: NodeTypesForProvider> DatabaseProvider<TX,
325327
// Must be written after blocks because of the receipt lookup.
326328
self.write_state(&execution_output, OriginalValuesKnown::No)?;
327329

328-
// insert hashes and intermediate merkle nodes
329-
self.write_hashed_state(&hashed_state)?;
330+
// aggregate hashed post states for batch insert
331+
aggregated_hashed_post_state_sorted.extend_ref(&hashed_state);
330332

331333
self.write_trie_changesets(block_number, &trie_updates, None)?;
332334
self.write_trie_updates_sorted(&trie_updates)?;
333335
}
334336

337+
// batch insert hash post states
338+
self.write_hashed_state(&aggregated_hashed_post_state_sorted)?;
339+
335340
// update history indices
336341
self.update_history_indices(first_number..=last_block_number)?;
337342

@@ -1711,7 +1716,7 @@ impl<TX: DbTxMut + DbTx + 'static, N: NodeTypesForProvider> StateWriter
17111716
) -> ProviderResult<()> {
17121717
// Write storage changes
17131718
tracing::trace!("Writing storage changes");
1714-
let mut storages_cursor = self.tx_ref().cursor_dup_write::<tables::PlainStorageState>()?;
1719+
let mut storages_cursor = self.tx_ref().cursor_dup_read::<tables::PlainStorageState>()?;
17151720
let mut storage_changeset_cursor =
17161721
self.tx_ref().cursor_dup_write::<tables::StorageChangeSets>()?;
17171722
for (block_index, mut storage_changes) in reverts.storage.into_iter().enumerate() {

crates/trie/common/src/utils.rs

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ use alloc::vec::Vec;
44
/// Values from `other` take precedence for duplicate keys.
55
///
66
/// This function efficiently merges two sorted vectors by:
7-
/// 1. Iterating through the target vector with mutable references
8-
/// 2. Using a peekable iterator for the other vector
9-
/// 3. For each target item, processing other items that come before or equal to it
10-
/// 4. Collecting items from other that need to be inserted
11-
/// 5. Appending and re-sorting only if new items were added
7+
/// 1. Using peekable iterators for both vectors to compare without consuming
8+
/// 2. Merging in one pass (O(n+m)) by always choosing the smaller element
9+
/// 3. Building the result in a pre-allocated vector
10+
/// 4. Replacing target with the merged result
1211
pub(crate) fn extend_sorted_vec<K, V>(target: &mut Vec<(K, V)>, other: &[(K, V)])
1312
where
1413
K: Clone + Ord,
@@ -18,36 +17,44 @@ where
1817
return;
1918
}
2019

20+
let mut result = Vec::with_capacity(target.len() + other.len());
21+
let mut target_iter = target.iter().peekable();
2122
let mut other_iter = other.iter().peekable();
22-
let mut to_insert = Vec::new();
2323

24-
// Iterate through target and update/collect items from other
25-
for target_item in target.iter_mut() {
26-
while let Some(other_item) = other_iter.peek() {
27-
use core::cmp::Ordering;
28-
match other_item.0.cmp(&target_item.0) {
29-
Ordering::Less => {
30-
// Other item comes before current target item, collect it
31-
to_insert.push(other_iter.next().unwrap().clone());
32-
}
33-
Ordering::Equal => {
34-
// Same key, update target with other's value
35-
target_item.1 = other_iter.next().unwrap().1.clone();
36-
break;
37-
}
38-
Ordering::Greater => {
39-
// Other item comes after current target item, keep target unchanged
40-
break;
24+
loop {
25+
match (target_iter.peek(), other_iter.peek()) {
26+
(Some(target_item), Some(other_item)) => {
27+
use core::cmp::Ordering;
28+
match other_item.0.cmp(&target_item.0) {
29+
Ordering::Less => {
30+
result.push(other_iter.next().unwrap().clone());
31+
}
32+
Ordering::Equal => {
33+
// duplicate key, use other's value (takes precedence)
34+
result.push(other_iter.next().unwrap().clone());
35+
target_iter.next();
36+
}
37+
Ordering::Greater => {
38+
result.push(target_iter.next().unwrap().clone());
39+
}
4140
}
4241
}
42+
(Some(_), None) => {
43+
// Only target items remaining
44+
result.extend(target_iter.cloned());
45+
break;
46+
}
47+
(None, Some(_)) => {
48+
// Only other items remaining
49+
result.extend(other_iter.cloned());
50+
break;
51+
}
52+
(None, None) => {
53+
// Both exhausted
54+
break;
55+
}
4356
}
4457
}
4558

46-
// Append collected new items, as well as any remaining from `other` which are necessarily also
47-
// new, and sort if needed
48-
if !to_insert.is_empty() || other_iter.peek().is_some() {
49-
target.extend(to_insert);
50-
target.extend(other_iter.cloned());
51-
target.sort_unstable_by(|a, b| a.0.cmp(&b.0));
52-
}
59+
*target = result;
5360
}

0 commit comments

Comments
 (0)