Skip to content

Implement libfunc profiler. #946

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
04314fd
Implement profiler for `x86_64`.
azteca1998 Nov 27, 2024
d023b33
Merge branch 'main' into add-libfunc-profiling
azteca1998 Nov 28, 2024
e9d5922
Add support for `armv8`.
azteca1998 Nov 28, 2024
c59cd54
Merge branch 'main' into add-libfunc-profiling
azteca1998 Jan 13, 2025
43dde6e
Merge branch 'main' into add-libfunc-profiling
azteca1998 Feb 18, 2025
edabf00
Merge branch 'main' into add-libfunc-profiling
edg-l Feb 20, 2025
bc031a9
merge main
FrancoGiachetta May 29, 2025
25bd806
add support for aot and contract executor
FrancoGiachetta May 29, 2025
87ace62
remove unneeded file
FrancoGiachetta May 29, 2025
34feaeb
fix clippy
FrancoGiachetta May 29, 2025
5513c99
add support for multiple profiles in a single execution
FrancoGiachetta May 30, 2025
a27f4fb
Merge branch 'main' into add-libfunc-profiling
FrancoGiachetta May 30, 2025
f34ab38
Merge branch 'main' into add-libfunc-profiling
FrancoGiachetta Jun 2, 2025
c3faebf
better documentation
FrancoGiachetta Jun 2, 2025
7d1bf3b
Merge branch 'add-libfunc-profiling' of github.com:lambdaclass/cairo_…
FrancoGiachetta Jun 2, 2025
e809e70
typo
FrancoGiachetta Jun 2, 2025
9b20cd5
make profiler_output optional
FrancoGiachetta Jun 2, 2025
b610940
Merge branch 'add-libfunc-profiling' of github.com:lambdaclass/cairo_…
FrancoGiachetta Jun 2, 2025
ed51045
clippy
FrancoGiachetta Jun 2, 2025
ce831bd
clippy
FrancoGiachetta Jun 2, 2025
95838ab
change sequencer and replay's hash in blocks workflow
FrancoGiachetta Jun 2, 2025
6e03189
forgot to remove libfunc-profilling from default features
FrancoGiachetta Jun 3, 2025
2ab1606
fix case with no deltas calculated
FrancoGiachetta Jun 9, 2025
714377a
change sequencer and replay hashes in starknet-blocks workflow
FrancoGiachetta Jun 9, 2025
4749448
Merge branch 'main' into add-libfunc-profiling
FrancoGiachetta Jun 9, 2025
743cb0e
add information about return values for x86
FrancoGiachetta Jun 10, 2025
c601909
Merge branch 'add-libfunc-profiling' of github.com:lambdaclass/cairo_…
FrancoGiachetta Jun 10, 2025
1c52393
Merge branch 'main' into add-libfunc-profiling
FrancoGiachetta Jun 10, 2025
6e99b28
reviews
FrancoGiachetta Jun 10, 2025
78b0179
Merge branch 'add-libfunc-profiling' of github.com:lambdaclass/cairo_…
FrancoGiachetta Jun 10, 2025
668ddf7
clippy
FrancoGiachetta Jun 10, 2025
69d90af
add documentation and change summarize_profiles to return a generic
FrancoGiachetta Jun 11, 2025
be18409
move LibfuncSummary to cairo-native-run
FrancoGiachetta Jun 11, 2025
44de79b
move LibfuncSummary to cairo-native-run
FrancoGiachetta Jun 11, 2025
19960d3
remove closure and return raw data
FrancoGiachetta Jun 12, 2025
d1f3594
remove closure and return raw data
FrancoGiachetta Jun 12, 2025
5426d1b
Merge branch 'main' into add-libfunc-profiling
FrancoGiachetta Jun 12, 2025
5cb9aa7
reviews
FrancoGiachetta Jun 12, 2025
5647bca
Merge branch 'add-libfunc-profiling' of github.com:lambdaclass/cairo_…
FrancoGiachetta Jun 12, 2025
6904f05
Merge branch 'add-libfunc-profiling' of github.com:lambdaclass/cairo_…
FrancoGiachetta Jun 12, 2025
ae97f9c
Merge branch 'add-libfunc-profiling' of github.com:lambdaclass/cairo_…
FrancoGiachetta Jun 12, 2025
243f874
add documentation about time deltas' unit
FrancoGiachetta Jun 12, 2025
0159c54
Merge branch 'main' into add-libfunc-profiling
FrancoGiachetta Jun 13, 2025
f6f362e
fix documentation in general
FrancoGiachetta Jun 13, 2025
76fd95a
Merge branch 'add-libfunc-profiling' of github.com:lambdaclass/cairo_…
FrancoGiachetta Jun 13, 2025
11bd05a
forgot to use None when no deltas were calculated
FrancoGiachetta Jun 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/starknet-blocks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
with:
repository: lambdaclass/starknet-replay
path: starknet-replay
ref: 1b8e2e0be21a8df9f5f6b8f8514d1a40b456ef58
ref: d36491aa5fca3f48b4d7fb25eba599603ff48225
# We need native to use the linux deps ci action
- name: Checkout Native
uses: actions/checkout@v4
Expand All @@ -43,8 +43,7 @@ jobs:
with:
repository: lambdaclass/sequencer
path: sequencer
ref: 40331042c1149f5cb84b27f9dd8d47994a010bbe

ref: 14be65ca995ac702bad26ac20f2a522d9515f70a
- name: Cache RPC Calls
uses: actions/cache@v4.2.0
with:
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ scarb = ["build-cli", "dep:scarb-ui", "dep:scarb-metadata"]
with-cheatcode = []
with-debug-utils = []
with-mem-tracing = []
with-libfunc-profiling = []
with-segfault-catcher = []
with-trace-dump = ["dep:sierra-emu"]

Expand Down
168 changes: 168 additions & 0 deletions src/bin/cairo-native-run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ use cairo_lang_compiler::{
compile_prepared_db, db::RootDatabase, project::setup_project, CompilerConfig,
};
use cairo_lang_runner::short_string::as_cairo_short_string;
#[cfg(feature = "with-libfunc-profiling")]
use cairo_lang_sierra::ids::ConcreteLibfuncId;
use cairo_lang_sierra_to_casm::metadata::MetadataComputationConfig;
#[cfg(feature = "with-libfunc-profiling")]
use cairo_native::metadata::profiler::LibfuncProfileData;
use cairo_native::{
context::NativeContext,
executor::{AotNativeExecutor, JitNativeExecutor},
metadata::gas::GasMetadata,
starknet_stub::StubSyscallHandler,
};
use clap::{Parser, ValueEnum};
#[cfg(feature = "with-libfunc-profiling")]
use std::collections::HashMap;
use std::path::PathBuf;
use tracing_subscriber::{EnvFilter, FmtSubscriber};
use utils::{find_function, result_to_runresult};
Expand Down Expand Up @@ -46,6 +52,11 @@ struct Args {
#[arg(short = 'O', long, default_value_t = 0)]
opt_level: u8,

#[cfg(feature = "with-libfunc-profiling")]
#[arg(long)]
/// The output path for the libfunc profilling results
profiler_output: Option<PathBuf>,

#[cfg(feature = "with-trace-dump")]
#[arg(long)]
/// The output path for the execution trace
Expand Down Expand Up @@ -131,6 +142,18 @@ fn main() -> anyhow::Result<()> {
}
}

#[cfg(feature = "with-libfunc-profiling")]
{
use cairo_native::metadata::profiler::ProfilerBinding;

if let Some(trace_id) =
executor.find_symbol_ptr(ProfilerBinding::ProfileId.symbol())
{
let trace_id = trace_id.cast::<u64>();
unsafe { *trace_id = 0 };
}
}

Box::new(move |function_id, args, gas, syscall_handler| {
executor.invoke_dynamic_with_syscall_handler(
function_id,
Expand All @@ -153,6 +176,16 @@ fn main() -> anyhow::Result<()> {
);
}

#[cfg(feature = "with-libfunc-profiling")]
{
use cairo_native::metadata::profiler::{ProfilerImpl, LIBFUNC_PROFILE};

LIBFUNC_PROFILE
.lock()
.unwrap()
.insert(0, ProfilerImpl::new());
}

let gas_metadata =
GasMetadata::new(&sierra_program, Some(MetadataComputationConfig::default())).unwrap();

Expand Down Expand Up @@ -188,6 +221,53 @@ fn main() -> anyhow::Result<()> {
println!("Remaining gas: {gas}");
}

#[cfg(feature = "with-libfunc-profiling")]
{
use std::{fs::File, io::Write};

let profile = cairo_native::metadata::profiler::LIBFUNC_PROFILE
.lock()
.unwrap();

assert_eq!(profile.values().len(), 1);

let profile = profile.values().next().unwrap();

if let Some(profiler_output_path) = args.profiler_output {
let mut output = File::create(profiler_output_path)?;

let raw_profiles = profile.get_profile(&sierra_program);
let mut processed_profiles = process_profiles(raw_profiles);

processed_profiles.sort_by_key(|LibfuncProfileSummary { libfunc_idx, .. }| {
sierra_program
.libfunc_declarations
.iter()
.enumerate()
.find_map(|(i, x)| (x.id == *libfunc_idx).then_some(i))
.unwrap()
});

for LibfuncProfileSummary {
libfunc_idx,
samples,
total_time,
average_time,
std_deviation,
quartiles,
} in processed_profiles
{
writeln!(output, "{libfunc_idx}")?;
writeln!(output, " Total Samples: {samples}")?;
writeln!(output, " Total Execution Time: {total_time}")?;
writeln!(output, " Average Execution Time: {average_time}")?;
writeln!(output, " Standard Deviation: {std_deviation}")?;
writeln!(output, " Quartiles: {quartiles:?}")?;
writeln!(output)?;
}
}
}

#[cfg(feature = "with-trace-dump")]
if let Some(trace_output) = args.trace_output {
let traces = cairo_native::metadata::trace_dump::trace_dump_runtime::TRACE_DUMP
Expand All @@ -205,3 +285,91 @@ fn main() -> anyhow::Result<()> {

Ok(())
}

#[cfg(feature = "with-libfunc-profiling")]
pub struct LibfuncProfileSummary {
pub libfunc_idx: ConcreteLibfuncId,
pub samples: u64,
pub total_time: u64,
pub average_time: f64,
pub std_deviation: f64,
pub quartiles: [u64; 5],
}

#[cfg(feature = "with-libfunc-profiling")]
fn process_profiles(
profiles: HashMap<ConcreteLibfuncId, LibfuncProfileData>,
) -> Vec<LibfuncProfileSummary> {
profiles
.into_iter()
.map(
|(
libfunc_idx,
LibfuncProfileData {
mut deltas,
extra_counts,
},
)| {
// if no deltas were registered, we only return the libfunc's calls amount
if deltas.is_empty() {
return LibfuncProfileSummary {
libfunc_idx,
samples: extra_counts,
total_time: 0,
average_time: 0.0,
std_deviation: 0.0,
quartiles: [0; 5],
};
}

deltas.sort();

// Drop outliers.
{
let q1 = deltas[deltas.len() / 4];
let q3 = deltas[3 * deltas.len() / 4];
let iqr = q3 - q1;

let q1_thr = q1.saturating_sub(iqr + iqr / 2);
let q3_thr = q3 + (iqr + iqr / 2);

deltas.retain(|x| *x >= q1_thr && *x <= q3_thr);
}

// Compute the quartiles.
let quartiles = [
*deltas.first().unwrap(),
deltas[deltas.len() / 4],
deltas[deltas.len() / 2],
deltas[3 * deltas.len() / 4],
*deltas.last().unwrap(),
];

// Compuite the average.
let average = deltas.iter().copied().sum::<u64>() as f64 / deltas.len() as f64;

// Compute the standard deviation.
let std_dev = {
let sum = deltas
.iter()
.copied()
.map(|x| x as f64)
.map(|x| (x - average))
.map(|x| x * x)
.sum::<f64>();
sum / (deltas.len() as u64 + extra_counts) as f64
};

LibfuncProfileSummary {
libfunc_idx,
samples: deltas.len() as u64 + extra_counts,
total_time: deltas.iter().sum::<u64>()
+ (extra_counts as f64 * average).round() as u64,
average_time: average,
std_deviation: std_dev,
quartiles,
}
},
)
.collect::<Vec<_>>()
}
65 changes: 45 additions & 20 deletions src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,26 +550,6 @@ fn compile_func(

let (state, _) = edit_state::take_args(state, invocation.args.iter())?;

let helper = LibfuncHelper {
module,
init_block: &pre_entry_block,
region: &region,
blocks_arena: &blocks_arena,
last_block: Cell::new(block),
branches: generate_branching_targets(
&blocks,
statements,
statement_idx,
invocation,
&state,
),
results: invocation
.branches
.iter()
.map(|x| vec![Cell::new(None); x.results.len()])
.collect::<Vec<_>>(),
};

let libfunc = registry.get_libfunc(&invocation.libfunc_id)?;
if is_recursive {
if let Some(target) = libfunc.is_function_call() {
Expand Down Expand Up @@ -620,6 +600,46 @@ fn compile_func(
}
}

#[allow(unused_mut)]
let mut helper = LibfuncHelper {
module,
init_block: &pre_entry_block,
region: &region,
blocks_arena: &blocks_arena,
last_block: Cell::new(block),
branches: generate_branching_targets(
&blocks,
statements,
statement_idx,
invocation,
&state,
),
results: invocation
.branches
.iter()
.map(|x| vec![Cell::new(None); x.results.len()])
.collect::<Vec<_>>(),

#[cfg(feature = "with-libfunc-profiling")]
profiler: match libfunc {
CoreConcreteLibfunc::FunctionCall(_) => {
// Tail-recursive function calls are broken beacuse a stack of timestamps is required,
// which would invalidate tail recursion. Also since each libfunc is measured individually,
// doesn't make sense to take function calls into account, therefore it's ignored on purpose.
None
}
_ => match metadata.remove::<crate::metadata::profiler::ProfilerMeta>()
{
Some(profiler_meta) => {
let t0 = profiler_meta
.measure_timestamp(context, block, location)?;
Some((profiler_meta, statement_idx, t0))
}
None => None,
},
},
};

libfunc.build(
context,
registry,
Expand Down Expand Up @@ -651,6 +671,11 @@ fn compile_func(
libfunc_name
);

#[cfg(feature = "with-libfunc-profiling")]
if let Some((profiler_meta, _, _)) = helper.profiler.take() {
metadata.insert(profiler_meta);
}

if let Some(tailrec_meta) = metadata.remove::<TailRecursionMeta>() {
if let Some(return_block) = tailrec_meta.return_target() {
tailrec_state = Some((tailrec_meta.depth_counter(), return_block));
Expand Down
3 changes: 3 additions & 0 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ impl NativeContext {
// already some metadata of the same type.
metadata.insert(gas_metadata);

#[cfg(feature = "with-libfunc-profiling")]
metadata.insert(crate::metadata::profiler::ProfilerMeta::new());

// Create the Sierra program registry
let registry = ProgramRegistry::<CoreType, CoreLibfunc>::new(program)?;

Expand Down
3 changes: 3 additions & 0 deletions src/executor/aot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ impl AotNativeExecutor {
#[cfg(feature = "with-trace-dump")]
crate::metadata::trace_dump::setup_runtime(|name| executor.find_symbol_ptr(name));

#[cfg(feature = "with-libfunc-profiling")]
crate::metadata::profiler::setup_runtime(|name| executor.find_symbol_ptr(name));

executor
}

Expand Down
3 changes: 3 additions & 0 deletions src/executor/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,9 @@ impl AotContractExecutor {
#[cfg(feature = "with-trace-dump")]
crate::metadata::trace_dump::setup_runtime(|name| executor.find_symbol_ptr(name));

#[cfg(feature = "with-libfunc-profiling")]
crate::metadata::profiler::setup_runtime(|name| executor.find_symbol_ptr(name));

Ok(Some(executor))
}

Expand Down
3 changes: 3 additions & 0 deletions src/executor/jit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ impl<'m> JitNativeExecutor<'m> {
#[cfg(feature = "with-trace-dump")]
crate::metadata::trace_dump::setup_runtime(|name| executor.find_symbol_ptr(name));

#[cfg(feature = "with-libfunc-profiling")]
crate::metadata::profiler::setup_runtime(|name| executor.find_symbol_ptr(name));

Ok(executor)
}

Expand Down
Loading
Loading