Skip to content

Commit 2cc03aa

Browse files
committed
Add per-phase heap profiling to prover instruments
1 parent 7b42e51 commit 2cc03aa

6 files changed

Lines changed: 100 additions & 6 deletions

File tree

bin/cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ tikv-jemalloc-ctl = { version = "0.6", features = ["stats"], optional = true }
1414

1515
[features]
1616
jemalloc-stats = ["dep:tikv-jemalloc-ctl"]
17-
instruments = ["prover/instruments"]
17+
instruments = ["prover/instruments", "stark/instruments"]

bin/cli/src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,12 @@ fn cmd_prove(elf_path: PathBuf, output_path: PathBuf, blowup: Option<u8>, time:
262262
#[cfg(feature = "jemalloc-stats")]
263263
let tracker = heap_tracker::HeapTracker::start();
264264

265+
#[cfg(all(feature = "jemalloc-stats", feature = "instruments"))]
266+
stark::instruments::set_heap_reader(|| {
267+
tikv_jemalloc_ctl::epoch::advance().ok();
268+
tikv_jemalloc_ctl::stats::allocated::read().unwrap_or(0)
269+
});
270+
265271
let start = Instant::now();
266272
let proof = match blowup {
267273
Some(b) => {

crypto/stark/src/instruments.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,30 @@
11
use std::cell::RefCell;
2+
use std::sync::OnceLock;
23
use std::sync::atomic::{AtomicU64, Ordering};
34
use std::time::Duration;
45

6+
static HEAP_READER: OnceLock<fn() -> usize> = OnceLock::new();
7+
8+
pub fn set_heap_reader(f: fn() -> usize) {
9+
let _ = HEAP_READER.set(f);
10+
}
11+
12+
pub fn heap_bytes() -> Option<usize> {
13+
HEAP_READER.get().map(|f| f())
14+
}
15+
16+
pub type HeapSnapshot = (&'static str, usize);
17+
18+
pub fn snap(label: &'static str) -> Option<HeapSnapshot> {
19+
heap_bytes().map(|b| (label, b))
20+
}
21+
22+
pub struct ProveHeapProfile {
23+
pub after_execute: Option<usize>,
24+
pub after_trace_build: Option<usize>,
25+
pub after_air: Option<usize>,
26+
}
27+
528
/// Sub-operation timing breakdown for a single table in Rounds 2-4.
629
#[derive(Clone, Debug, Default)]
730
pub struct TableSubOps {
@@ -49,6 +72,7 @@ pub struct MultiProveTiming {
4972
pub round1_sub: Round1SubOps,
5073
/// (name, rows, duration, sub_ops) per table for rounds 2-4.
5174
pub table_timings: Vec<(String, usize, Duration, TableSubOps)>,
75+
pub heap_snapshots: Vec<HeapSnapshot>,
5276
}
5377

5478
/// Round 1 sub-timings: atomics so parallel rayon workers can accumulate safely.

crypto/stark/src/prover.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,6 +1496,8 @@ pub trait IsStarkProver<
14961496

14971497
#[cfg(feature = "instruments")]
14981498
crate::instruments::reset_all();
1499+
#[cfg(feature = "instruments")]
1500+
let mut heap_snaps: Vec<crate::instruments::HeapSnapshot> = Vec::new();
14991501

15001502
let num_airs = air_trace_pairs.len();
15011503

@@ -1547,6 +1549,10 @@ pub trait IsStarkProver<
15471549

15481550
#[cfg(feature = "instruments")]
15491551
let prepass_elapsed = phase_start.elapsed();
1552+
#[cfg(feature = "instruments")]
1553+
if let Some(s) = crate::instruments::snap("after pool alloc") {
1554+
heap_snaps.push(s);
1555+
}
15501556

15511557
// =====================================================================
15521558
// Round 1, Phase A: Commit all main traces (parallel in chunks of K)
@@ -1609,6 +1615,10 @@ pub trait IsStarkProver<
16091615

16101616
#[cfg(feature = "instruments")]
16111617
let main_commits_elapsed = phase_start.elapsed();
1618+
#[cfg(feature = "instruments")]
1619+
if let Some(s) = crate::instruments::snap("after main commits") {
1620+
heap_snaps.push(s);
1621+
}
16121622

16131623
// =====================================================================
16141624
// Round 1, Phase B: Sample shared LogUp challenges
@@ -1655,6 +1665,10 @@ pub trait IsStarkProver<
16551665

16561666
#[cfg(feature = "instruments")]
16571667
let aux_build_elapsed = phase_start.elapsed();
1668+
#[cfg(feature = "instruments")]
1669+
if let Some(s) = crate::instruments::snap("after aux build") {
1670+
heap_snaps.push(s);
1671+
}
16581672

16591673
// Pass 2: Parallel fork transcript → extract → LDE → commit in chunks of K.
16601674
// Each table gets its own transcript fork and pool set.
@@ -1755,6 +1769,10 @@ pub trait IsStarkProver<
17551769

17561770
#[cfg(feature = "instruments")]
17571771
let aux_commit_elapsed = phase_start.elapsed();
1772+
#[cfg(feature = "instruments")]
1773+
if let Some(s) = crate::instruments::snap("after aux commit") {
1774+
heap_snaps.push(s);
1775+
}
17581776

17591777
#[cfg(feature = "debug-checks")]
17601778
{
@@ -1897,6 +1915,7 @@ pub trait IsStarkProver<
18971915
rounds_2_4: phase_start.elapsed(),
18981916
round1_sub: crate::instruments::take_r1_sub(),
18991917
table_timings,
1918+
heap_snapshots: heap_snaps,
19001919
});
19011920
}
19021921

prover/src/instruments.rs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ pub fn print_report(
5757
trace_build: Duration,
5858
air_construction: Duration,
5959
total: Duration,
60+
heap_before: Option<usize>,
61+
heap_profile: &stark::instruments::ProveHeapProfile,
6062
) {
6163
let mp = stark::instruments::take();
6264

@@ -69,18 +71,18 @@ pub fn print_report(
6971
row_top("Trace build", trace_build, total);
7072
row_top("AIR construction", air_construction, total);
7173

72-
if let Some(mp) = mp {
74+
if let Some(ref mp) = mp {
7375
let round1 = mp.main_commits + mp.aux_build + mp.aux_commit;
7476

7577
row_top("Pre-pass (domains/twiddles)", mp.prepass, total);
7678
row_top("Round 1", round1, total);
7779
row_sub(" Main trace commits", mp.main_commits, total);
78-
row_sub(" expand_pool_to_lde", mp.round1_sub.main_lde, total);
79-
row_sub(" commit (Merkle)", mp.round1_sub.main_merkle, total);
80+
row_sub(" Main expand_pool_to_lde", mp.round1_sub.main_lde, total);
81+
row_sub(" Main commit (Merkle)", mp.round1_sub.main_merkle, total);
8082
row_sub(" Aux trace build (parallel)", mp.aux_build, total);
8183
row_sub(" Aux trace commit", mp.aux_commit, total);
82-
row_sub(" expand_pool_to_lde", mp.round1_sub.aux_lde, total);
83-
row_sub(" commit (Merkle)", mp.round1_sub.aux_merkle, total);
84+
row_sub(" Aux expand_pool_to_lde", mp.round1_sub.aux_lde, total);
85+
row_sub(" Aux commit (Merkle)", mp.round1_sub.aux_merkle, total);
8486
row_top("Rounds 2\u{2013}4", mp.rounds_2_4, total);
8587

8688
// Merge split tables: MEMW[0..4] → MEMW x5
@@ -214,4 +216,33 @@ pub fn print_report(
214216
eprintln!(" {}", "─".repeat(58));
215217
eprintln!(" {:<36} {:>7.2}s", "TOTAL", total.as_secs_f64());
216218
eprintln!();
219+
220+
let mb = |b: usize| b / (1024 * 1024);
221+
if let Some(before) = heap_before {
222+
eprintln!("=== HEAP PROFILE (MB) ===");
223+
eprintln!(" {:<36} {:>8} {:>8}", "Phase", "Heap", "Delta");
224+
eprintln!(" {}", "─".repeat(56));
225+
let mut prev = before;
226+
let mut print_row = |label: &str, val: Option<usize>| {
227+
if let Some(v) = val {
228+
let cur = mb(v);
229+
let delta = mb(v) as isize - mb(prev) as isize;
230+
eprintln!(" {:<36} {:>7} {:>+8}", label, cur, delta);
231+
prev = v;
232+
}
233+
};
234+
print_row("After execute", heap_profile.after_execute);
235+
print_row("After trace build", heap_profile.after_trace_build);
236+
print_row("After AIR construction", heap_profile.after_air);
237+
if let Some(ref mp_data) = mp {
238+
for (label, bytes) in &mp_data.heap_snapshots {
239+
let cur = mb(*bytes);
240+
let delta = cur as isize - mb(prev) as isize;
241+
eprintln!(" {:<36} {:>7} {:>+8}", label, cur, delta);
242+
prev = *bytes;
243+
}
244+
}
245+
eprintln!(" {}", "─".repeat(56));
246+
eprintln!();
247+
}
217248
}

prover/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,8 @@ pub fn prove_with_options(
494494
) -> Result<VmProof, Error> {
495495
#[cfg(feature = "instruments")]
496496
let total_start = std::time::Instant::now();
497+
#[cfg(feature = "instruments")]
498+
let heap_before = stark::instruments::heap_bytes();
497499

498500
// Phase 1: Execute (ELF load + run)
499501
#[cfg(feature = "instruments")]
@@ -507,6 +509,8 @@ pub fn prove_with_options(
507509

508510
#[cfg(feature = "instruments")]
509511
let execute_elapsed = phase_start.elapsed();
512+
#[cfg(feature = "instruments")]
513+
let heap_after_execute = stark::instruments::heap_bytes();
510514

511515
// Phase 2: Trace build
512516
#[cfg(feature = "instruments")]
@@ -518,6 +522,8 @@ pub fn prove_with_options(
518522

519523
#[cfg(feature = "instruments")]
520524
let trace_build_elapsed = phase_start.elapsed();
525+
#[cfg(feature = "instruments")]
526+
let heap_after_trace = stark::instruments::heap_bytes();
521527

522528
// Phase 3: AIR construction
523529
#[cfg(feature = "instruments")]
@@ -534,6 +540,8 @@ pub fn prove_with_options(
534540

535541
#[cfg(feature = "instruments")]
536542
let air_elapsed = phase_start.elapsed();
543+
#[cfg(feature = "instruments")]
544+
let heap_after_air = stark::instruments::heap_bytes();
537545

538546
let runtime_page_ranges = traces.runtime_page_ranges();
539547

@@ -551,6 +559,12 @@ pub fn prove_with_options(
551559
trace_build_elapsed,
552560
air_elapsed,
553561
total_start.elapsed(),
562+
heap_before,
563+
&stark::instruments::ProveHeapProfile {
564+
after_execute: heap_after_execute,
565+
after_trace_build: heap_after_trace,
566+
after_air: heap_after_air,
567+
},
554568
);
555569
}
556570

0 commit comments

Comments
 (0)