Skip to content

Commit

Permalink
perf: store hints linearly
Browse files Browse the repository at this point in the history
This avoids costly `HashMap` queries
  • Loading branch information
Oppen committed Jul 3, 2023
1 parent 1932562 commit 9829f65
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 124 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#### Upcoming Changes

* 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. This reduces run time by up to 12% in proof mode.
BREAKING:

#### [0.8.1] - 2023-6-29

* chore: change mentions of *cairo-rs-py* to *cairo-vm-py* [#1296](https://github.com/lambdaclass/cairo-vm/pull/1296)
Expand Down
79 changes: 55 additions & 24 deletions vm/src/serde/deserialize_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::{
SIGNATURE_BUILTIN_NAME,
},
};
use core::num::NonZeroUsize;
use felt::{Felt252, PRIME_STR};
use num_traits::float::FloatCore;
use num_traits::{Num, Pow};
Expand Down Expand Up @@ -418,9 +419,41 @@ pub fn parse_program_json(
}
}

let mut program_hints: Vec<_> = program_json
.hints
.into_iter()
.filter(|(_, v)| !v.is_empty())
.collect();
program_hints.sort_unstable_by(|x, y| x.0.cmp(&y.0));

let mut hints_ranges = Vec::new();
hints_ranges.resize(program_hints.last().map(|(x, _)| x + 1).unwrap_or(0), None);

let hints_ranges_iter = program_hints
.iter()
.map(|(k, v)| (k, v.len()))
.scan(0, |s, (k, v)| {
let res = (
k,
(
*s,
NonZeroUsize::new(v).expect("empty vecs already filtered"),
),
);
*s += v;
Some(res)
});

for (pc, r) in hints_ranges_iter {
hints_ranges[*pc] = Some(r);
}

let hints = program_hints.into_iter().flat_map(|(_, v)| v).collect();

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 @@ -616,7 +649,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 @@ -812,10 +845,8 @@ mod tests {
MaybeRelocatable::Int(Felt252::new(2345108766317314046_i64)),
];

let mut hints: HashMap<usize, Vec<HintParams>> = HashMap::new();
hints.insert(
0,
vec![HintParams {
let hints = vec![
HintParams {
code: "memory[ap] = segments.add()".to_string(),
accessible_scopes: vec![
String::from("starkware.cairo.common.alloc"),
Expand All @@ -828,11 +859,8 @@ mod tests {
},
reference_ids: HashMap::new(),
},
}],
);
hints.insert(
46,
vec![HintParams {
},
HintParams {
code: "import math".to_string(),
accessible_scopes: vec![String::from("__main__"), String::from("__main__.main")],
flow_tracking_data: FlowTrackingData {
Expand All @@ -842,13 +870,17 @@ mod tests {
},
reference_ids: HashMap::new(),
},
}],
);
},
];
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);
assert_eq!(program.shared_program_data.hints_ranges, hints_ranges);
}

/// Deserialize a program without an entrypoint.
Expand All @@ -871,10 +903,8 @@ mod tests {
MaybeRelocatable::Int(Felt252::new(2345108766317314046_i64)),
];

let mut hints: HashMap<usize, Vec<HintParams>> = HashMap::new();
hints.insert(
0,
vec![HintParams {
let hints = vec![
HintParams {
code: "memory[ap] = segments.add()".to_string(),
accessible_scopes: vec![
String::from("starkware.cairo.common.alloc"),
Expand All @@ -887,11 +917,8 @@ mod tests {
},
reference_ids: HashMap::new(),
},
}],
);
hints.insert(
46,
vec![HintParams {
},
HintParams {
code: "import math".to_string(),
accessible_scopes: vec![String::from("__main__"), String::from("__main__.main")],
flow_tracking_data: FlowTrackingData {
Expand All @@ -901,13 +928,17 @@ mod tests {
},
reference_ids: HashMap::new(),
},
}],
);
},
];
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, None);
assert_eq!(program.shared_program_data.hints, hints);
assert_eq!(program.shared_program_data.hints_ranges, hints_ranges);
}

#[test]
Expand Down
109 changes: 106 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 @@ -43,7 +44,8 @@ use std::path::Path;
#[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 @@ -83,12 +85,39 @@ impl Program {
constants.insert(key.clone(), value);
}
}

let mut hints: Vec<_> = hints.into_iter().filter(|(_, v)| !v.is_empty()).collect();
hints.sort_unstable_by(|x, y| x.0.cmp(&y.0));

let hints_ranges_iter = hints
.iter()
.map(|(k, v)| (k, v.len()))
.scan(0, |s, (k, v)| {
let res = (
k,
(
*s,
NonZeroUsize::new(v).expect("empty vecs already filtered"),
),
);
*s += v;
Some(res)
});

let mut hints_ranges = vec![None; hints.last().map(|(x, _)| x + 1).unwrap_or(0)];
for (pc, r) in hints_ranges_iter {
hints_ranges[*pc] = Some(r);
}

let hints = hints.into_iter().flat_map(|(_, v)| v).collect();

let shared_program_data = SharedProgramData {
data,
hints,
main,
start: None,
end: None,
hints,
hints_ranges,
error_message_attributes,
instruction_locations,
identifiers,
Expand Down Expand Up @@ -267,6 +296,79 @@ 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,
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());
assert_eq!(
program.shared_program_data.hints,
vec!["a", "b", "c", "d"]
.into_iter()
.map(str_to_hint_param)
.collect::<Vec<_>>(),
);
assert_eq!(
program.shared_program_data.hints_ranges,
vec![
None,
Some((0, NonZeroUsize::new(1usize).unwrap())),
None,
None,
Some((1, NonZeroUsize::new(1usize).unwrap())),
Some((2, NonZeroUsize::new(2usize).unwrap())),
]
);
}

#[test]
Expand Down Expand Up @@ -870,7 +972,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 9829f65

Please sign in to comment.