-
Notifications
You must be signed in to change notification settings - Fork 47
Add compilation stats #1339
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
base: main
Are you sure you want to change the base?
Add compilation stats #1339
Changes from 35 commits
8d91878
47026ac
b546c91
314a4fa
f9e223f
9f4ceaa
89f60b7
319e533
a2859e1
f6402c7
f8c4b7e
2e816c1
e07e01e
7d20787
6ccb11a
32a047d
dd29aa6
0c8e562
9381d9f
cd9c418
be7f0ac
0d14a00
83afe53
aa1c762
d298e8c
1cb390a
647e2d9
25fea5a
65eec68
ddb303b
7e959ce
6c7a10b
4cc0901
a7710d3
2cc0d61
24d8fe4
78fe43f
5642941
c404dd4
5580b49
bd5e08f
4979bce
642552b
ceeb6a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,7 @@ use crate::{ | |
| arch::AbiArgument, | ||
| clone_option_mut, | ||
| context::NativeContext, | ||
| debug::libfunc_to_name, | ||
| debug::{libfunc_to_name, type_to_name}, | ||
| error::{panic::ToNativeAssertError, Error, Result}, | ||
| execution_result::{ | ||
| BuiltinStats, ContractExecutionResult, ADD_MOD_BUILTIN_SIZE, BITWISE_BUILTIN_SIZE, | ||
|
|
@@ -47,11 +47,11 @@ use crate::{ | |
| module::NativeModule, | ||
| native_assert, native_panic, | ||
| starknet::{handler::StarknetSyscallHandlerCallbacks, StarknetSyscallHandler}, | ||
| statistics::Statistics, | ||
| statistics::{SierraDeclaredTypeStats, SierraFuncStats, Statistics}, | ||
| types::TypeBuilder, | ||
| utils::{ | ||
| decode_error_message, generate_function_name, get_integer_layout, libc_free, libc_malloc, | ||
| BuiltinCosts, | ||
| decode_error_message, generate_function_name, get_integer_layout, get_types_total_size, | ||
| libc_free, libc_malloc, BuiltinCosts, | ||
| }, | ||
| OptLevel, | ||
| }; | ||
|
|
@@ -62,6 +62,7 @@ use cairo_lang_sierra::{ | |
| core::{CoreLibfunc, CoreType, CoreTypeConcrete}, | ||
| gas::CostTokenType, | ||
| starknet::StarknetTypeConcrete, | ||
| ConcreteLibfunc, | ||
| }, | ||
| ids::FunctionId, | ||
| program::{GenFunction, GenStatement, Program, StatementIdx}, | ||
|
|
@@ -80,7 +81,7 @@ use starknet_types_core::felt::Felt; | |
| use std::{ | ||
| alloc::Layout, | ||
| cmp::Ordering, | ||
| collections::BTreeMap, | ||
| collections::{BTreeMap, HashSet}, | ||
| ffi::c_void, | ||
| fs::{self, File}, | ||
| io, | ||
|
|
@@ -240,13 +241,72 @@ impl AotContractExecutor { | |
| )?; | ||
|
|
||
| if let Some(&mut ref mut stats) = stats { | ||
| for type_declaration in &program.type_declarations { | ||
| if let Ok(type_concrete) = registry.get_type(&type_declaration.id) { | ||
| let type_id = type_declaration.id.to_string(); | ||
| let type_size = type_concrete.layout(®istry).unwrap().size(); | ||
| if !type_concrete.is_builtin() { | ||
| // We dont want to add the builtins to the stats | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are builtins skipped? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I figured they are always the same size. So I though it didn't make much sense to have them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its not worth treating them any differently. There are many types that are always the same size (i.e. u8). I would keep them, it would also make the code a bit simpler. |
||
| stats.sierra_declared_types_stats.insert( | ||
| type_id, | ||
| SierraDeclaredTypeStats { | ||
| concrete_type: type_to_name( | ||
| ®istry, | ||
| type_concrete, | ||
| HashSet::new(), | ||
| ), | ||
| size: type_size, | ||
| as_param_count: 0, | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| if let CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(info)) = | ||
| type_concrete | ||
| { | ||
| stats.add_circuit_gates(&info.circuit_info); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| for statement in &program.statements { | ||
| if let GenStatement::Invocation(invocation) = statement { | ||
| let libfunc = registry.get_libfunc(&invocation.libfunc_id)?; | ||
| let name = libfunc_to_name(libfunc).to_string(); | ||
| *stats.sierra_libfunc_frequency.entry(name).or_insert(0) += 1; | ||
|
|
||
| for param in libfunc.param_signatures() { | ||
| let param_ty = param.ty.to_string(); | ||
| if let Some(type_stats) = | ||
| stats.sierra_declared_types_stats.get_mut(¶m_ty) | ||
| { | ||
| type_stats.as_param_count += 1; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| for func in &program.funcs { | ||
| let func_id = func.id.to_string(); | ||
| // Params | ||
| let params_quant = func.params.len(); | ||
| let params_total_size = | ||
| get_types_total_size(&func.signature.param_types, ®istry); | ||
| // Return types | ||
| let return_types_quant = func.signature.ret_types.len(); | ||
| let return_types_total_size = | ||
| get_types_total_size(&func.signature.ret_types, ®istry); | ||
|
|
||
| stats.sierra_func_stats.insert( | ||
| func_id, | ||
| SierraFuncStats { | ||
| params_quant, | ||
| params_total_size, | ||
| return_types_quant, | ||
| return_types_total_size, | ||
| }, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // Generate mappings between the entry point's selectors and their function indexes. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| use std::collections::BTreeMap; | ||
|
|
||
| use cairo_lang_sierra::extensions::circuit::{CircuitInfo, GateOffsets}; | ||
| use serde::Serialize; | ||
| use std::collections::BTreeMap; | ||
|
|
||
| /// A set of compilation statistics gathered during the compilation. | ||
| /// It should be completely filled at the end of the compilation. | ||
|
|
@@ -14,8 +14,14 @@ pub struct Statistics { | |
| pub sierra_statement_count: Option<usize>, | ||
| /// Number of user functions defined in the Sierra code. | ||
| pub sierra_func_count: Option<usize>, | ||
| /// Stats of the declared types in Sierra. | ||
| pub sierra_declared_types_stats: BTreeMap<String, SierraDeclaredTypeStats>, | ||
| /// Stats about params and return types of each Sierra function. | ||
| pub sierra_func_stats: BTreeMap<String, SierraFuncStats>, | ||
| /// Number of statements for each distinct libfunc. | ||
| pub sierra_libfunc_frequency: BTreeMap<String, u128>, | ||
| /// Number of times each circuit gate is used. | ||
| sierra_circuit_gates_count: CircuitGatesStats, | ||
| /// Number of MLIR operations generated. | ||
|
Comment on lines
+25
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this field be public? |
||
| pub mlir_operation_count: Option<u128>, | ||
| /// Number of MLIR operations generated for each distinct libfunc. | ||
|
|
@@ -44,6 +50,40 @@ pub struct Statistics { | |
| pub object_size_bytes: Option<usize>, | ||
| } | ||
|
|
||
| /// Contains the stats about a Sierra function: | ||
| /// - params_quant: Quantity of params | ||
| /// - params_total_size: Total size of all the params | ||
| /// - return_types_quant: Quantity of return types | ||
| /// - return_types_total_size: Total size of all the params | ||
| #[derive(Debug, Default, Serialize)] | ||
| pub struct SierraFuncStats { | ||
| pub params_quant: usize, | ||
| pub params_total_size: usize, | ||
| pub return_types_quant: usize, | ||
| pub return_types_total_size: usize, | ||
| } | ||
|
|
||
| /// Contains the stats for each Sierra declared type: | ||
| /// - concrete_type: The concrete type (e.g Struct) | ||
| /// - size: Layout size of the whole type | ||
| /// - as_param_count: Number of times the type is used as a param in a libfunc | ||
| #[derive(Debug, Default, Serialize)] | ||
| pub struct SierraDeclaredTypeStats { | ||
| pub concrete_type: String, | ||
| pub size: usize, | ||
| pub as_param_count: usize, | ||
| } | ||
|
|
||
| /// Contains the quantity of each circuit gate | ||
| /// in a contract | ||
| #[derive(Debug, Default, Serialize)] | ||
DiegoCivi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| struct CircuitGatesStats { | ||
| add_gate: usize, | ||
| sub_gate: usize, | ||
| mul_gate: usize, | ||
| inverse_gate: usize, | ||
| } | ||
DiegoCivi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| impl Statistics { | ||
| pub fn validate(&self) -> bool { | ||
| self.sierra_type_count.is_some() | ||
|
|
@@ -62,6 +102,63 @@ impl Statistics { | |
| && self.compilation_linking_time_ms.is_some() | ||
| && self.object_size_bytes.is_some() | ||
| } | ||
|
|
||
| /// Counts the gates in a circuit. It uses the same algorithm used | ||
| /// to evaluate the gates on a circuit when evaluating it. | ||
| pub fn add_circuit_gates(&mut self, info: &CircuitInfo) { | ||
| let mut known_gates = vec![false; 1 + info.n_inputs + info.values.len()]; | ||
| known_gates[0] = true; | ||
| for i in 0..info.n_inputs { | ||
| known_gates[i + 1] = true; | ||
| } | ||
|
|
||
| let mut add_offsets = info.add_offsets.iter().peekable(); | ||
| let mut mul_offsets = info.mul_offsets.iter(); | ||
|
|
||
| loop { | ||
| while let Some(&add_gate_offset) = add_offsets.peek() { | ||
| let lhs = known_gates[add_gate_offset.lhs].to_owned(); | ||
| let rhs = known_gates[add_gate_offset.rhs].to_owned(); | ||
| let output = known_gates[add_gate_offset.output].to_owned(); | ||
|
|
||
| match (lhs, rhs, output) { | ||
| (true, true, false) => { | ||
| // ADD | ||
| self.sierra_circuit_gates_count.add_gate += 1; | ||
| known_gates[add_gate_offset.output] = true; | ||
| } | ||
| (false, true, true) => { | ||
| // SUB | ||
| self.sierra_circuit_gates_count.sub_gate += 1; | ||
| known_gates[add_gate_offset.lhs] = true; | ||
| } | ||
| _ => break, | ||
| } | ||
| add_offsets.next(); | ||
| } | ||
|
|
||
| if let Some(&GateOffsets { lhs, rhs, output }) = mul_offsets.next() { | ||
| let lhs_value = known_gates[lhs]; | ||
| let rhs_value = known_gates[rhs]; | ||
| let output_value = known_gates[output]; | ||
|
|
||
| match (lhs_value, rhs_value, output_value) { | ||
| (true, true, false) => { | ||
| // MUL | ||
| self.sierra_circuit_gates_count.mul_gate += 1; | ||
| known_gates[output] = true; | ||
| } | ||
| (false, true, true) => { | ||
| self.sierra_circuit_gates_count.inverse_gate += 1; | ||
| known_gates[lhs] = true; | ||
| } | ||
| _ => panic!("Imposible circuit"), // It should never reach this point, since it would have failed in the compilation before | ||
| } | ||
JulianGCalderon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } else { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Clones a variable of type `Option<&mut T>` without consuming self | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.