Skip to content

Commit 4dc1b5c

Browse files
Rjectedfgimenez
andauthored
perf: spawn prewarming transactions in chunks (paradigmxyz#15155)
Co-authored-by: Federico Gimenez <[email protected]>
1 parent abd8981 commit 4dc1b5c

File tree

3 files changed

+44
-79
lines changed

3 files changed

+44
-79
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/engine/tree/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ rayon.workspace = true
6060
tracing.workspace = true
6161
derive_more.workspace = true
6262
parking_lot.workspace = true
63+
itertools.workspace = true
6364

6465
# optional deps for test-utils
6566
reth-prune-types = { workspace = true, optional = true }

crates/engine/tree/src/tree/payload_processor/prewarm.rs

+42-79
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::tree::{
1010
use alloy_consensus::transaction::Recovered;
1111
use alloy_evm::Database;
1212
use alloy_primitives::{keccak256, map::B256Set, B256};
13+
use itertools::Itertools;
1314
use metrics::{Gauge, Histogram};
1415
use reth_evm::{ConfigureEvm, Evm, EvmFor};
1516
use reth_metrics::Metrics;
@@ -37,8 +38,6 @@ pub(super) struct PrewarmCacheTask<N: NodePrimitives, P, Evm> {
3738
pending: VecDeque<Recovered<N::SignedTx>>,
3839
/// Context provided to execution tasks
3940
ctx: PrewarmContext<N, P, Evm>,
40-
/// How many txs are currently in progress
41-
in_progress: usize,
4241
/// How many transactions should be executed in parallel
4342
max_concurrency: usize,
4443
/// Sender to emit evm state outcome messages, if any.
@@ -69,7 +68,6 @@ where
6968
execution_cache,
7069
pending,
7170
ctx,
72-
in_progress: 0,
7371
max_concurrency: 64,
7472
to_multi_proof,
7573
actions_rx,
@@ -82,45 +80,19 @@ where
8280
self.actions_tx.clone()
8381
}
8482

85-
/// Spawns the next transactions
86-
fn spawn_next(&mut self) {
87-
while self.in_progress < self.max_concurrency {
88-
if let Some(tx) = self.pending.pop_front() {
89-
// increment the in progress counter
90-
self.in_progress += 1;
83+
/// Spawns all pending transactions as blocking tasks by first chunking them.
84+
fn spawn_all(&mut self) {
85+
let chunk_size = (self.pending.len() / self.max_concurrency).max(1);
9186

92-
self.spawn_transaction(tx);
93-
} else {
94-
break
95-
}
96-
}
97-
}
98-
99-
/// Spawns the given transaction as a blocking task.
100-
fn spawn_transaction(&self, tx: Recovered<N::SignedTx>) {
101-
let ctx = self.ctx.clone();
102-
let metrics = self.ctx.metrics.clone();
103-
let actions_tx = self.actions_tx.clone();
104-
let prepare_proof_targets = self.should_prepare_multi_proof_targets();
87+
for chunk in &self.pending.drain(..).chunks(chunk_size) {
88+
let sender = self.actions_tx.clone();
89+
let ctx = self.ctx.clone();
90+
let pending_chunk = chunk.collect::<Vec<_>>();
10591

106-
self.executor.spawn_blocking(move || {
107-
let start = Instant::now();
108-
// depending on whether this task needs he proof targets we either just transact or
109-
// transact and prepare the targets
110-
let proof_targets = if prepare_proof_targets {
111-
ctx.prepare_multiproof_targets(tx)
112-
} else {
113-
ctx.transact(tx);
114-
None
115-
};
116-
let _ = actions_tx.send(PrewarmTaskEvent::Outcome { proof_targets });
117-
metrics.total_runtime.record(start.elapsed());
118-
});
119-
}
120-
121-
/// Returns true if the tx prewarming tasks should prepare multiproof targets.
122-
fn should_prepare_multi_proof_targets(&self) -> bool {
123-
self.to_multi_proof.is_some()
92+
self.executor.spawn_blocking(move || {
93+
ctx.transact_batch(&pending_chunk, sender);
94+
});
95+
}
12496
}
12597

12698
/// If configured and the tx returned proof targets, emit the targets the transaction produced
@@ -160,7 +132,7 @@ where
160132
self.ctx.metrics.transactions_histogram.record(self.pending.len() as f64);
161133

162134
// spawn execution tasks.
163-
self.spawn_next();
135+
self.spawn_all();
164136

165137
while let Ok(event) = self.actions_rx.recv() {
166138
match event {
@@ -170,7 +142,6 @@ where
170142
}
171143
PrewarmTaskEvent::Outcome { proof_targets } => {
172144
// completed a transaction, frees up one slot
173-
self.in_progress -= 1;
174145
self.send_multi_proof_targets(proof_targets);
175146
}
176147
PrewarmTaskEvent::Terminate { block_output } => {
@@ -182,9 +153,6 @@ where
182153
break
183154
}
184155
}
185-
186-
// schedule followup transactions
187-
self.spawn_next();
188156
}
189157
}
190158
}
@@ -207,17 +175,6 @@ where
207175
P: BlockReader + StateProviderFactory + StateReader + StateCommitmentProvider + Clone + 'static,
208176
Evm: ConfigureEvm<Primitives = N> + 'static,
209177
{
210-
/// Transacts the transactions and transform the state into [`MultiProofTargets`].
211-
fn prepare_multiproof_targets(self, tx: Recovered<N::SignedTx>) -> Option<MultiProofTargets> {
212-
let metrics = self.metrics.clone();
213-
let state = self.transact(tx)?;
214-
215-
let (targets, storage_targets) = multiproof_targets_from_state(state);
216-
metrics.prefetch_storage_targets.record(storage_targets as f64);
217-
218-
Some(targets)
219-
}
220-
221178
/// Splits this context into an evm, an evm config, and metrics.
222179
fn evm_for_ctx(self) -> Option<(EvmFor<Evm, impl Database>, Evm, PrewarmMetrics)> {
223180
let Self { header, evm_config, cache: caches, cache_metrics, provider, metrics } = self;
@@ -252,35 +209,41 @@ where
252209
Some((evm, evm_config, metrics))
253210
}
254211

255-
/// Transacts the transaction and returns the state outcome.
212+
/// Transacts the vec of transactions and returns the state outcome.
256213
///
257-
/// Returns `None` if executing the transaction failed to a non Revert error.
214+
/// Returns `None` if executing the transactions failed to a non Revert error.
258215
/// Returns the touched+modified state of the transaction.
259216
///
260-
/// Note: Since here are no ordering guarantees this won't the state the tx produces when
217+
/// Note: Since here are no ordering guarantees this won't the state the txs produce when
261218
/// executed sequentially.
262-
fn transact(self, tx: Recovered<N::SignedTx>) -> Option<EvmState> {
263-
let (mut evm, evm_config, metrics) = self.evm_for_ctx()?;
219+
fn transact_batch(self, txs: &[Recovered<N::SignedTx>], sender: Sender<PrewarmTaskEvent>) {
220+
let Some((mut evm, evm_config, metrics)) = self.evm_for_ctx() else { return };
264221

265-
// create the tx env and reset nonce
266-
let tx_env = evm_config.tx_env(&tx);
267-
let start = Instant::now();
268-
let res = match evm.transact(tx_env) {
269-
Ok(res) => res,
270-
Err(err) => {
271-
trace!(
272-
target: "engine::tree",
273-
%err,
274-
tx_hash=%tx.tx_hash(),
275-
sender=%tx.signer(),
276-
"Error when executing prewarm transaction",
277-
);
278-
return None
279-
}
280-
};
281-
metrics.execution_duration.record(start.elapsed());
222+
for tx in txs {
223+
// create the tx env
224+
let tx_env = evm_config.tx_env(tx);
225+
let start = Instant::now();
226+
let res = match evm.transact(tx_env) {
227+
Ok(res) => res,
228+
Err(err) => {
229+
trace!(
230+
target: "engine::tree",
231+
%err,
232+
tx_hash=%tx.tx_hash(),
233+
sender=%tx.signer(),
234+
"Error when executing prewarm transaction",
235+
);
236+
return
237+
}
238+
};
239+
metrics.execution_duration.record(start.elapsed());
282240

283-
Some(res.state)
241+
let (targets, storage_targets) = multiproof_targets_from_state(res.state);
242+
metrics.prefetch_storage_targets.record(storage_targets as f64);
243+
metrics.total_runtime.record(start.elapsed());
244+
245+
let _ = sender.send(PrewarmTaskEvent::Outcome { proof_targets: Some(targets) });
246+
}
284247
}
285248
}
286249

0 commit comments

Comments
 (0)