diff --git a/book/profiling.png b/book/profiling.png new file mode 100644 index 000000000..06d6617c8 Binary files /dev/null and b/book/profiling.png differ diff --git a/book/writing-programs/cycle-tracking.md b/book/writing-programs/cycle-tracking.md index e423565e6..9a61ae767 100644 --- a/book/writing-programs/cycle-tracking.md +++ b/book/writing-programs/cycle-tracking.md @@ -58,20 +58,24 @@ This will log the cycle count for `block name` and include it in the `ExecutionR ### Profiling a ZKVM program -Profiling a zkvm program is a good way to get an understanding of what is bottlenecking your program, note only one program may be profiled at a time. +Profiling a zkvm program is a good way to get an understanding of what is bottlenecking your program. Note only one program may be profiled at a time. -To profile a program, you have to setup a script to execute the program, -many examples can be found in the repo, such as this ['fibonacci'](https://github.com/succinctlabs/sp1/blob/12f212e386ae4c2da30cf6a61a7d87615d56bdac/examples/fibonacci/script/src/main.rs#L22) script. +To profile a program, you have to setup a script to execute the program. -Once you have your script it should contain the following code: +Many examples can be found in the repo, such as this ['fibonacci'](https://github.com/succinctlabs/sp1/blob/12f212e386ae4c2da30cf6a61a7d87615d56bdac/examples/fibonacci/script/src/main.rs#L22) script. + +Once you have your script it should look like the following: ```rs // Execute the program using the `ProverClient.execute` method, without generating a proof. let (_, report) = client.execute(ELF, stdin.clone()).run().unwrap(); ``` -The data captured by the profiler can be quite large, you can set the sample rate using the `TRACE_SAMPLE_RATE` env var. -To enable profiling, set the `TRACE_FILE` env var to the path where you want the profile to be saved. +As well you must enable the profiling feature on the SDK: +```toml + sp1-sdk = { version = "3.0.0", features = ["profiling"] } +``` +The `TRACE_FILE` env var tells the executor where to save the profile, and the `TRACE_SAMPLE_RATE` env var tells the executor how often to sample the program. A larger sample rate will give you a smaller profile, it is the number of instructions in between each sample. The full command to profile should look something like this @@ -85,4 +89,5 @@ To view these profiles, we recommend [Samply](https://github.com/mstange/samply) samply load output.json ``` -Samply uses the firefox profiler to create a nice visualization for your programs execution. +Samply uses the Firefox profiler to create a nice visualization of your programs execution. +![An example screenshot of the Firefox Profiler](../profiling.png) diff --git a/crates/core/executor/src/executor.rs b/crates/core/executor/src/executor.rs index fb088799a..f483aeb97 100644 --- a/crates/core/executor/src/executor.rs +++ b/crates/core/executor/src/executor.rs @@ -111,7 +111,9 @@ pub struct Executor<'a> { /// A buffer for stdout and stderr IO. pub io_buf: HashMap, - /// The ZKVM profiler. + /// The ZKVM program profiler. + /// + /// Keeps track of the number of cycles spent in each function. #[cfg(feature = "profiling")] pub profiler: Option<(Profiler, BufWriter)>, @@ -192,7 +194,7 @@ impl<'a> Executor<'a> { Self::with_context(program, opts, SP1Context::default()) } - /// Crete a new runtime for the program, and setup the profiler if `TRACE_FILE` env var is set + /// Create a new runtime for the program, and setup the profiler if `TRACE_FILE` env var is set /// and the feature flag `profiling` is enabled. #[must_use] pub fn with_context_and_elf( diff --git a/crates/core/executor/src/profiler.rs b/crates/core/executor/src/profiler.rs index ed17a6073..b1fbfd0b6 100644 --- a/crates/core/executor/src/profiler.rs +++ b/crates/core/executor/src/profiler.rs @@ -14,8 +14,6 @@ pub enum ProfilerError { Serde(#[from] serde_json::Error), } -/// The ZKVM Profiler. -/// /// During execution, the profiler always keeps track of the callstack /// and will occasionally save the stack according to the sample rate. pub struct Profiler { @@ -51,8 +49,8 @@ impl Profiler { let mut function_ranges = Vec::new(); let mut builder = ThreadBuilder::new(1, 0, std::time::Instant::now(), false, false); - // We need to extract all the functions from the elf file - // and thier corresponding PC ranges. + // We need to extract all the functions from the ELF file + // and their corresponding PC ranges. let mut main_idx = None; for sym in &elf.syms { // check if its a function @@ -63,7 +61,7 @@ impl Profiler { let start_address = sym.st_value; let end_address = start_address + size - 4; - // Now that we have the name lets immediately intern it so we only need to copy + // Now that we have the name let's immediately intern it so we only need to copy // around a usize let demangled_name = demangled_name.to_string(); let string_idx = builder.intern_string(&demangled_name); @@ -180,7 +178,7 @@ impl Profiler { last_known_time, sample.stack.into_iter(), // We don't have a way to know the duration of each sample, so we just use 1us for - // all instructions + // all instructions. std::time::Duration::from_micros(self.sample_rate), );