Skip to content

Commit

Permalink
perf: store hints linearly (lambdaclass#931)
Browse files Browse the repository at this point in the history
This avoids costly `HashMap` queries
  • Loading branch information
Oppen authored and kariy committed Jul 25, 2023
1 parent 3e697f6 commit 5f065e3
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 167 deletions.
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 @@ -452,9 +452,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 Down Expand Up @@ -518,6 +521,7 @@ impl From<Program> for ProgramJson {
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 @@ -688,7 +692,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 @@ -865,6 +869,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 @@ -884,43 +907,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 @@ -943,43 +976,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 struct SharedProgramData {
pub data: Vec<MaybeRelocatable>,
pub hints: HashMap<usize, Vec<HintParams>>,
pub hints: Vec<HintParams>,
pub hints_ranges: Vec<Option<(usize, NonZeroUsize)>>,
pub main: Option<usize>,
//start and end labels will only be used in proof-mode
pub 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

0 comments on commit 5f065e3

Please sign in to comment.