Skip to content

Commit 12569b4

Browse files
committed
feat(evm): add a pipeline profiler
1 parent a4821d7 commit 12569b4

File tree

6 files changed

+181
-2
lines changed

6 files changed

+181
-2
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ doctest = false
1515
anyhow = "1.0"
1616
thiserror = "2.0"
1717
semver = "1.0"
18-
serde = { version = "1.0", "features" = [ "derive" ] }
18+
serde = { version = "1.0", features = [ "derive" ] }
1919
num = "0.4"
2020
itertools = "0.14"
21+
indexmap = { version = "2.10", features = ["serde"] }
2122

2223
zkevm_opcode_defs = "=0.150.6"
2324

src/evm/context/mod.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::context::r#loop::Loop;
2121
use crate::context::IContext;
2222
use crate::debug_config::DebugConfig;
2323
use crate::evm::build::Build as EVMBuild;
24+
use crate::evm::profiler::Profiler;
2425
use crate::evm::warning::Warning;
2526
use crate::optimizer::settings::Settings as OptimizerSettings;
2627
use crate::optimizer::Optimizer;
@@ -121,10 +122,17 @@ impl<'ctx> Context<'ctx> {
121122
output_assembly: bool,
122123
output_bytecode: bool,
123124
is_size_fallback: bool,
125+
profiler: &mut Profiler,
124126
) -> anyhow::Result<EVMBuild> {
125127
let module_clone = self.module.clone();
126128
let contract_path = self.module.get_name().to_str().expect("Always valid");
127129

130+
let run_init_verify = profiler.start_evm_translation_unit(
131+
contract_path,
132+
self.code_segment,
133+
"InitVerify",
134+
self.optimizer.settings(),
135+
);
128136
let target_machine = TargetMachine::new(
129137
era_compiler_common::Target::EVM,
130138
self.optimizer.settings(),
@@ -158,7 +166,14 @@ impl<'ctx> Context<'ctx> {
158166
self.code_segment,
159167
)
160168
})?;
169+
run_init_verify.borrow_mut().finish();
161170

171+
let run_optimize_verify = profiler.start_evm_translation_unit(
172+
contract_path,
173+
self.code_segment,
174+
"OptimizeVerify",
175+
self.optimizer.settings(),
176+
);
162177
self.optimizer
163178
.run(&target_machine, self.module())
164179
.map_err(|error| anyhow::anyhow!("{} code optimizing: {error}", self.code_segment))?;
@@ -176,8 +191,15 @@ impl<'ctx> Context<'ctx> {
176191
self.code_segment,
177192
)
178193
})?;
194+
run_optimize_verify.borrow_mut().finish();
179195

180196
let assembly_buffer = if output_assembly || self.debug_config.is_some() {
197+
let run_emit_llvm_assembly = profiler.start_evm_translation_unit(
198+
contract_path,
199+
self.code_segment,
200+
"EmitLLVMAssembly",
201+
self.optimizer.settings(),
202+
);
181203
let assembly_buffer = target_machine
182204
.write_to_memory_buffer(self.module(), inkwell::targets::FileType::Assembly)
183205
.map_err(|error| anyhow::anyhow!("assembly emitting: {error}"))?;
@@ -193,6 +215,7 @@ impl<'ctx> Context<'ctx> {
193215
)?;
194216
}
195217

218+
run_emit_llvm_assembly.borrow_mut().finish();
196219
Some(assembly_buffer)
197220
} else {
198221
None
@@ -201,11 +224,18 @@ impl<'ctx> Context<'ctx> {
201224
.map(|assembly_buffer| String::from_utf8_lossy(assembly_buffer.as_slice()).to_string());
202225

203226
if output_bytecode {
227+
let run_emit_bytecode = profiler.start_evm_translation_unit(
228+
contract_path,
229+
self.code_segment,
230+
"EmitBytecode",
231+
self.optimizer.settings(),
232+
);
204233
let bytecode_buffer = target_machine
205234
.write_to_memory_buffer(self.module(), inkwell::targets::FileType::Object)
206235
.map_err(|error| {
207236
anyhow::anyhow!("{} bytecode emitting: {error}", self.code_segment)
208237
})?;
238+
run_emit_bytecode.borrow_mut().finish();
209239

210240
let immutables = match self.code_segment {
211241
era_compiler_common::CodeSegment::Deploy => None,
@@ -242,7 +272,7 @@ impl<'ctx> Context<'ctx> {
242272
for function in self.module.get_functions() {
243273
Function::set_size_attributes(self.llvm, function);
244274
}
245-
return self.build(output_assembly, output_bytecode, true);
275+
return self.build(output_assembly, output_bytecode, true, profiler);
246276
} else {
247277
warnings.push(match self.code_segment {
248278
era_compiler_common::CodeSegment::Deploy => Warning::DeployCodeSize {

src/evm/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod build;
66
pub mod r#const;
77
pub mod context;
88
pub mod instructions;
9+
pub mod profiler;
910
pub mod warning;
1011

1112
use std::collections::BTreeMap;

src/evm/profiler/mod.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//!
2+
//! Compiler pipeline profiler.
3+
//!
4+
5+
pub mod run;
6+
7+
use std::cell::RefCell;
8+
use std::rc::Rc;
9+
10+
use indexmap::IndexMap;
11+
12+
use crate::optimizer::settings::Settings as OptimizerSettings;
13+
14+
use self::run::Run;
15+
16+
///
17+
/// Compiler pipeline profiler.
18+
///
19+
#[derive(Debug, Default)]
20+
pub struct Profiler {
21+
/// Indexed map of timing entries.
22+
pub timings: IndexMap<String, Rc<RefCell<Run>>>,
23+
}
24+
25+
impl Profiler {
26+
///
27+
/// Starts a new run for a generic part of the pipeline.
28+
///
29+
pub fn start_pipeline_element(&mut self, description: &str) -> Rc<RefCell<Run>> {
30+
let run_name = description.to_owned();
31+
assert!(
32+
!self.timings.contains_key(run_name.as_str()),
33+
"Translation unit run `{run_name}` already exists"
34+
);
35+
36+
self.start_run(run_name)
37+
}
38+
39+
///
40+
/// Starts a new run for an EVM translation unit.
41+
///
42+
pub fn start_evm_translation_unit(
43+
&mut self,
44+
full_path: &str,
45+
code_segment: era_compiler_common::CodeSegment,
46+
description: &str,
47+
optimizer_settings: &OptimizerSettings,
48+
) -> Rc<RefCell<Run>> {
49+
let spill_area_description = format!(
50+
"SpillArea({})",
51+
optimizer_settings.spill_area_size().unwrap_or_default()
52+
);
53+
let run_name = format!(
54+
"{full_path}:{code_segment}/{description}/{optimizer_settings}/{spill_area_description}",
55+
);
56+
assert!(
57+
!self.timings.contains_key(run_name.as_str()),
58+
"Translation unit run `{run_name}` already exists"
59+
);
60+
61+
self.start_run(run_name)
62+
}
63+
64+
///
65+
/// Returns a serializeable vector of the profiler runs.
66+
///
67+
pub fn to_vec(&self) -> Vec<(String, u64)> {
68+
self.timings
69+
.iter()
70+
.map(|(name, run)| {
71+
let run = run.borrow();
72+
(
73+
name.clone(),
74+
run.duration.expect("Always exists").as_millis() as u64,
75+
)
76+
})
77+
.collect()
78+
}
79+
80+
///
81+
/// Starts a new run with the given name.
82+
///
83+
fn start_run(&mut self, name: String) -> Rc<RefCell<Run>> {
84+
assert!(
85+
!self.timings.contains_key(name.as_str()),
86+
"Run `{name}` already exists"
87+
);
88+
89+
let run = Rc::new(RefCell::new(Run::default()));
90+
self.timings.insert(name, run.clone());
91+
run
92+
}
93+
}

src/evm/profiler/run.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//!
2+
//! Compiler pipeline profiler run.
3+
//!
4+
5+
use std::time::Duration;
6+
use std::time::Instant;
7+
8+
///
9+
/// Compiler pipeline profiler run.
10+
///
11+
#[derive(Debug)]
12+
pub struct Run {
13+
/// Start time.
14+
pub start_time: Instant,
15+
/// Recorded duration.
16+
pub duration: Option<Duration>,
17+
}
18+
19+
impl Default for Run {
20+
fn default() -> Self {
21+
Run {
22+
start_time: Instant::now(),
23+
duration: None,
24+
}
25+
}
26+
}
27+
28+
impl Run {
29+
///
30+
/// Records the duration of the run.
31+
///
32+
pub fn finish(&mut self) {
33+
assert!(
34+
self.duration.is_none(),
35+
"Duration has already been recorded"
36+
);
37+
38+
self.duration = Some(self.start_time.elapsed());
39+
}
40+
}
41+
42+
impl std::fmt::Display for Run {
43+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44+
let duration = self
45+
.duration
46+
.as_ref()
47+
.expect("Duration has not been recorded yet");
48+
49+
write!(f, "{duration:?}")?;
50+
Ok(())
51+
}
52+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ pub use self::evm::instructions::return_data as evm_return_data;
109109
pub use self::evm::instructions::storage as evm_storage;
110110
pub use self::evm::link as evm_link;
111111
pub use self::evm::minimal_deploy_code as evm_minimal_deploy_code;
112+
pub use self::evm::profiler::run::Run as EVMProfilerRun;
113+
pub use self::evm::profiler::Profiler as EVMProfiler;
112114
pub use self::evm::r#const as evm_const;
113115
pub use self::evm::warning::Warning as EVMWarning;
114116
pub use self::evm::DummyLLVMWritable as EVMDummyLLVMWritable;

0 commit comments

Comments
 (0)