Skip to content
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

perf: represent pc:hint mapping as a vector indexed by ranges #931

Merged
merged 4 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@

#### Upcoming Changes

* feat: add `arbitrary` feature to enable arbitrary derive in `Program` and `CairoRunConfig`

* feat(fuzzing): add `arbitrary` feature to enable arbitrary derive in `Program` and `CairoRunConfig` [#1306](https://github.com/lambdaclass/cairo-vm/pull/1306) [#1330](https://github.com/lambdaclass/cairo-vm/pull/1330)

* perf: remove pointless iterator from rc limits tracking [#1316](https://github.com/lambdaclass/cairo-vm/pull/1316)

* feat(felt): add `from_bytes_le` and `from_bytes_ne` methods to `Felt252` [#1326](https://github.com/lambdaclass/cairo-vm/pull/1326)

* perf: change `Program::shared_program_data::hints` from `HashMap<usize, Vec<Box<dyn Any>>>` to `Vec<Box<dyn Any>>` and refer to them as ranges stored in a `Vec<_>` indexed by PC with run time reductions of up to 12% [#931](https://github.com/lambdaclass/cairo-vm/pull/931)
BREAKING:
* `get_hint_dictionary(&self, &[HintReference], &mut dyn HintProcessor) -> Result<HashMap<usize, Vec<Box<dyn Any>>, VirtualMachineError>` ->
`get_hint_data(self, &[HintReference], &mut dyn HintProcessor) -> Result<Vec<Box<dyn Any>, VirtualMachineError>`
* Hook methods receive `&[Box<dyn Any>]` rather than `&HashMap<usize, Vec<Box<dyn Any>>>`

#### [0.8.2] - 2023-7-10

* chore: update dependencies, particularly lamdaworks 0.1.2 -> 0.1.3 [#1323](https://github.com/lambdaclass/cairo-vm/pull/1323)
Expand Down
168 changes: 104 additions & 64 deletions vm/src/serde/deserialize_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,9 +433,12 @@ pub fn parse_program_json(
}
}

let (hints, hints_ranges) = Program::flatten_hints(&program_json.hints);

let shared_program_data = SharedProgramData {
data: program_json.data,
hints: program_json.hints,
hints,
hints_ranges,
main: entrypoint_pc,
start,
end,
Expand All @@ -461,6 +464,7 @@ pub fn parse_program_json(
mod tests {
use super::*;
use assert_matches::assert_matches;
use core::num::NonZeroUsize;
use felt::felt_str;
use num_traits::One;
use num_traits::Zero;
Expand Down Expand Up @@ -631,7 +635,7 @@ mod tests {
MaybeRelocatable::Int(Felt252::new(2345108766317314046_i64)),
];

let mut hints: HashMap<usize, Vec<HintParams>> = HashMap::new();
let mut hints = HashMap::new();
hints.insert(
0,
vec![HintParams {
Expand Down Expand Up @@ -808,6 +812,25 @@ mod tests {
);
}

fn get_hints_as_map(program: &Program) -> HashMap<usize, Vec<HintParams>> {
let (hints, ranges) = (
&program.shared_program_data.hints,
&program.shared_program_data.hints_ranges,
);
let mut hints_map = HashMap::new();

for (pc, range) in ranges.iter().enumerate() {
let Some((start, len)) = range else {
continue;
};
// Associate the PC with its corresponding hints by mapping them
// to the elements in the proper range converted to vec.
hints_map.insert(pc, hints[*start..start + len.get()].to_vec());
}

hints_map
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn deserialize_program_test() {
Expand All @@ -827,43 +850,53 @@ mod tests {
MaybeRelocatable::Int(Felt252::new(2345108766317314046_i64)),
];

let mut hints: HashMap<usize, Vec<HintParams>> = HashMap::new();
hints.insert(
0,
vec![HintParams {
code: "memory[ap] = segments.add()".to_string(),
accessible_scopes: vec![
String::from("starkware.cairo.common.alloc"),
String::from("starkware.cairo.common.alloc.alloc"),
],
flow_tracking_data: FlowTrackingData {
ap_tracking: ApTracking {
group: 0,
offset: 0,
let hints: HashMap<_, _> = [
(
0,
vec![HintParams {
code: "memory[ap] = segments.add()".to_string(),
accessible_scopes: vec![
String::from("starkware.cairo.common.alloc"),
String::from("starkware.cairo.common.alloc.alloc"),
],
flow_tracking_data: FlowTrackingData {
ap_tracking: ApTracking {
group: 0,
offset: 0,
},
reference_ids: HashMap::new(),
},
reference_ids: HashMap::new(),
},
}],
);
hints.insert(
46,
vec![HintParams {
code: "import math".to_string(),
accessible_scopes: vec![String::from("__main__"), String::from("__main__.main")],
flow_tracking_data: FlowTrackingData {
ap_tracking: ApTracking {
group: 5,
offset: 0,
}],
),
(
46,
vec![HintParams {
code: "import math".to_string(),
accessible_scopes: vec![
String::from("__main__"),
String::from("__main__.main"),
],
flow_tracking_data: FlowTrackingData {
ap_tracking: ApTracking {
group: 5,
offset: 0,
},
reference_ids: HashMap::new(),
},
reference_ids: HashMap::new(),
},
}],
);
}],
),
]
.into();
let mut hints_ranges = vec![None; 47];
hints_ranges[0] = Some((0, NonZeroUsize::new(1).unwrap()));
hints_ranges[46] = Some((1, NonZeroUsize::new(1).unwrap()));

assert_eq!(program.builtins, builtins);
assert_eq!(program.shared_program_data.data, data);
assert_eq!(program.shared_program_data.main, Some(0));
assert_eq!(program.shared_program_data.hints, hints);

let program_hints = get_hints_as_map(&program);
assert_eq!(program_hints, hints);
}

/// Deserialize a program without an entrypoint.
Expand All @@ -886,43 +919,50 @@ mod tests {
MaybeRelocatable::Int(Felt252::new(2345108766317314046_i64)),
];

let mut hints: HashMap<usize, Vec<HintParams>> = HashMap::new();
hints.insert(
0,
vec![HintParams {
code: "memory[ap] = segments.add()".to_string(),
accessible_scopes: vec![
String::from("starkware.cairo.common.alloc"),
String::from("starkware.cairo.common.alloc.alloc"),
],
flow_tracking_data: FlowTrackingData {
ap_tracking: ApTracking {
group: 0,
offset: 0,
let hints: HashMap<_, _> = [
(
0,
vec![HintParams {
code: "memory[ap] = segments.add()".to_string(),
accessible_scopes: vec![
String::from("starkware.cairo.common.alloc"),
String::from("starkware.cairo.common.alloc.alloc"),
],
flow_tracking_data: FlowTrackingData {
ap_tracking: ApTracking {
group: 0,
offset: 0,
},
reference_ids: HashMap::new(),
},
reference_ids: HashMap::new(),
},
}],
);
hints.insert(
46,
vec![HintParams {
code: "import math".to_string(),
accessible_scopes: vec![String::from("__main__"), String::from("__main__.main")],
flow_tracking_data: FlowTrackingData {
ap_tracking: ApTracking {
group: 5,
offset: 0,
}],
),
(
46,
vec![HintParams {
code: "import math".to_string(),
accessible_scopes: vec![
String::from("__main__"),
String::from("__main__.main"),
],
flow_tracking_data: FlowTrackingData {
ap_tracking: ApTracking {
group: 5,
offset: 0,
},
reference_ids: HashMap::new(),
},
reference_ids: HashMap::new(),
},
}],
);
}],
),
]
.into();

assert_eq!(program.builtins, builtins);
assert_eq!(program.shared_program_data.data, data);
assert_eq!(program.shared_program_data.main, None);
assert_eq!(program.shared_program_data.hints, hints);

let program_hints = get_hints_as_map(&program);
assert_eq!(program_hints, hints);
}

#[test]
Expand Down
105 changes: 102 additions & 3 deletions vm/src/types/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
};
#[cfg(feature = "cairo-1-hints")]
use cairo_lang_starknet::casm_contract_class::CasmContractClass;
use core::num::NonZeroUsize;
use felt::{Felt252, PRIME_STR};

#[cfg(feature = "std")]
Expand Down Expand Up @@ -47,7 +48,8 @@ use arbitrary::Arbitrary;
#[derive(Clone, Default, Debug, PartialEq, Eq)]
pub(crate) struct SharedProgramData {
pub(crate) data: Vec<MaybeRelocatable>,
pub(crate) hints: HashMap<usize, Vec<HintParams>>,
pub(crate) hints: Vec<HintParams>,
pub(crate) hints_ranges: Vec<Option<(usize, NonZeroUsize)>>,
pub(crate) main: Option<usize>,
//start and end labels will only be used in proof-mode
pub(crate) start: Option<usize>,
Expand Down Expand Up @@ -88,12 +90,16 @@ impl Program {
constants.insert(key.clone(), value);
}
}

let (hints, hints_ranges) = Self::flatten_hints(&hints);

let shared_program_data = SharedProgramData {
data,
hints,
main,
start: None,
end: None,
hints,
hints_ranges,
error_message_attributes,
instruction_locations,
identifiers,
Expand All @@ -106,6 +112,33 @@ impl Program {
})
}

pub(crate) fn flatten_hints(
hints: &HashMap<usize, Vec<HintParams>>,
) -> (Vec<HintParams>, Vec<Option<(usize, NonZeroUsize)>>) {
let bounds = hints
.iter()
.map(|(pc, hs)| (*pc, hs.len()))
.reduce(|(max_pc, full_len), (pc, len)| (max_pc.max(pc), full_len + len));

let Some((max_pc, full_len)) = bounds else {
return (Vec::new(), Vec::new());
};

let mut hints_values = Vec::with_capacity(full_len);
let mut hints_ranges = vec![None; max_pc + 1];

for (pc, hs) in hints.iter().filter(|(_, hs)| !hs.is_empty()) {
let range = (
hints_values.len(),
NonZeroUsize::new(hs.len()).expect("empty vecs already filtered"),
);
hints_ranges[*pc] = Some(range);
hints_values.extend_from_slice(&hs[..]);
}

(hints_values, hints_ranges)
}

#[cfg(feature = "std")]
pub fn from_file(path: &Path, entrypoint: Option<&str>) -> Result<Program, ProgramError> {
let file_content = std::fs::read(path)?;
Expand Down Expand Up @@ -272,6 +305,71 @@ mod tests {
assert_eq!(program.shared_program_data.data, data);
assert_eq!(program.shared_program_data.main, None);
assert_eq!(program.shared_program_data.identifiers, HashMap::new());
assert_eq!(program.shared_program_data.hints, Vec::new());
assert_eq!(program.shared_program_data.hints_ranges, Vec::new());
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn new_program_with_hints() {
let reference_manager = ReferenceManager {
references: Vec::new(),
};

let builtins: Vec<BuiltinName> = Vec::new();
let data: Vec<MaybeRelocatable> = vec![
mayberelocatable!(5189976364521848832),
mayberelocatable!(1000),
mayberelocatable!(5189976364521848832),
mayberelocatable!(2000),
mayberelocatable!(5201798304953696256),
mayberelocatable!(2345108766317314046),
];

let str_to_hint_param = |s: &str| HintParams {
code: s.to_string(),
accessible_scopes: vec![],
flow_tracking_data: FlowTrackingData {
ap_tracking: ApTracking {
group: 0,
offset: 0,
},
reference_ids: HashMap::new(),
},
};

let hints = HashMap::from([
(5, vec![str_to_hint_param("c"), str_to_hint_param("d")]),
(1, vec![str_to_hint_param("a")]),
(4, vec![str_to_hint_param("b")]),
]);

let program = Program::new(
builtins.clone(),
data.clone(),
None,
hints.clone(),
reference_manager,
HashMap::new(),
Vec::new(),
None,
)
.unwrap();

assert_eq!(program.builtins, builtins);
assert_eq!(program.shared_program_data.data, data);
assert_eq!(program.shared_program_data.main, None);
assert_eq!(program.shared_program_data.identifiers, HashMap::new());

let program_hints: HashMap<_, _> = program
.shared_program_data
.hints_ranges
.iter()
.enumerate()
.filter_map(|(pc, r)| r.map(|(s, l)| (pc, (s, s + l.get()))))
.map(|(pc, (s, e))| (pc, program.shared_program_data.hints[s..e].to_vec()))
.collect();
assert_eq!(program_hints, hints);
}

#[test]
Expand Down Expand Up @@ -875,7 +973,8 @@ mod tests {
fn default_program() {
let shared_program_data = SharedProgramData {
data: Vec::new(),
hints: HashMap::new(),
hints: Vec::new(),
hints_ranges: Vec::new(),
main: None,
start: None,
end: None,
Expand Down
Loading
Loading