Skip to content

Commit 1d8a3e7

Browse files
Test memory optimization using Arc<Task>.
1 parent 2a75773 commit 1d8a3e7

File tree

6 files changed

+58
-39
lines changed

6 files changed

+58
-39
lines changed

.github/workflows/upload_artifacts_workflow.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: Upload-Artifacts
33
on:
44
push:
55
branches:
6-
- or/bump_version_bitwise_mem_optimization
6+
- or/test_mem_optimization_using_arc
77

88
jobs:
99
artifacts-push:

crates/cairo-program-runner-lib/src/hints/execute_task_hints.rs

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::any::Any;
22
use std::collections::HashMap;
3+
use std::sync::Arc;
34
use std::vec;
45

56
use cairo_lang_runner::{Arg, CairoHintProcessor};
@@ -82,8 +83,8 @@ pub fn load_program_hint(
8283
ap_tracking,
8384
)?;
8485

85-
let task: Task = exec_scopes.get(vars::TASK)?;
86-
let program = get_program_from_task(&task)?;
86+
let task: &Arc<Task> = exec_scopes.get_ref(vars::TASK)?;
87+
let program = get_program_from_task(task)?;
8788

8889
let program_header_ptr = get_ptr_from_var_name("program_header", vm, ids_data, ap_tracking)?;
8990

@@ -125,7 +126,8 @@ pub fn append_fact_topologies(
125126
ids_data: &HashMap<String, HintReference>,
126127
ap_tracking: &ApTracking,
127128
) -> Result<(), HintError> {
128-
let task: Task = exec_scopes.get(vars::TASK)?;
129+
// Get Arc<Task> here (cheap clone) since we need mutable access to exec_scopes for fact_topologies.
130+
let task: Arc<Task> = exec_scopes.get(vars::TASK)?;
129131
let output_runner_data: Option<OutputBuiltinState> =
130132
exec_scopes.get(vars::OUTPUT_RUNNER_DATA)?;
131133
let fact_topologies: &mut Vec<FactTopology> = exec_scopes.get_mut_ref(vars::FACT_TOPOLOGIES)?;
@@ -162,8 +164,8 @@ pub fn validate_hash(
162164
ids_data: &HashMap<String, HintReference>,
163165
ap_tracking: &ApTracking,
164166
) -> Result<(), HintError> {
165-
let task: Task = exec_scopes.get(vars::TASK)?;
166-
let program = get_program_from_task(&task)?;
167+
let task: &Arc<Task> = exec_scopes.get_ref(vars::TASK)?;
168+
let program = get_program_from_task(task)?;
167169

168170
let output_ptr = get_ptr_from_var_name("output_ptr", vm, ids_data, ap_tracking)?;
169171
let program_hash_ptr = (output_ptr + 1)?;
@@ -301,11 +303,11 @@ pub fn write_return_builtins_hint(
301303
ids_data: &HashMap<String, HintReference>,
302304
ap_tracking: &ApTracking,
303305
) -> Result<(), HintError> {
304-
let task: Task = exec_scopes.get(vars::TASK)?;
306+
let task: &Arc<Task> = exec_scopes.get_ref(vars::TASK)?;
305307
let n_builtins: usize = exec_scopes.get(vars::N_BUILTINS)?;
306308

307309
// builtins = task.get_program().builtins
308-
let program = get_program_from_task(&task)?;
310+
let program = get_program_from_task(task)?;
309311
let builtins = &program.builtins;
310312

311313
// write_return_builtins(
@@ -325,7 +327,7 @@ pub fn write_return_builtins_hint(
325327
builtins,
326328
used_builtins_addr,
327329
pre_execution_builtins_addr,
328-
&task,
330+
task,
329331
)?;
330332

331333
// vm_enter_scope({'n_selected_builtins': n_builtins})
@@ -398,7 +400,8 @@ pub fn setup_subtask_for_execution(
398400
ids_data: &HashMap<String, HintReference>,
399401
ap_tracking: &ApTracking,
400402
) -> Result<HintExtension, HintError> {
401-
let task: Task = exec_scopes.get(vars::TASK)?;
403+
// Get Arc<Task> (cheap clone) since we need mutable access to exec_scopes throughout this function.
404+
let task: Arc<Task> = exec_scopes.get(vars::TASK)?;
402405

403406
let n_builtins = get_program_from_task(&task)?.builtins.len();
404407
exec_scopes.insert_value(vars::N_BUILTINS, n_builtins);
@@ -408,7 +411,7 @@ pub fn setup_subtask_for_execution(
408411
let mut hint_extension = HintExtension::default();
409412

410413
let subtask_cairo1_hint_processor: Option<CairoHintProcessor>;
411-
match &task {
414+
match &*task {
412415
Task::Cairo0Program(cairo0_executable) => {
413416
if let Some(program_input) = cairo0_executable.program_input.as_ref() {
414417
new_task_locals.insert(PROGRAM_INPUT.to_string(), any_box![program_input.clone()]);
@@ -530,8 +533,8 @@ pub fn bootloader_validate_hash(
530533
return Ok(());
531534
}
532535

533-
let task: Task = exec_scopes.get(vars::TASK)?;
534-
let program = get_program_from_task(&task)?;
536+
let task: &Arc<Task> = exec_scopes.get_ref(vars::TASK)?;
537+
let program = get_program_from_task(task)?;
535538
let output_ptr = get_ptr_from_var_name("output_ptr", vm, ids_data, ap_tracking)?;
536539
let program_hash_ptr = (output_ptr + 1)?;
537540
let program_hash = vm.get_integer(program_hash_ptr)?.into_owned();
@@ -598,10 +601,10 @@ mod tests {
598601
/// pointers in the ids_data point to it.
599602
#[rstest]
600603
fn test_allocation_in_load_program_hint(fibonacci: Program) {
601-
let fibonacci_task = Task::Cairo0Program(Cairo0Executable {
604+
let fibonacci_task = Arc::new(Task::Cairo0Program(Cairo0Executable {
602605
program: fibonacci.clone(),
603606
program_input: None,
604-
});
607+
}));
605608
let (
606609
mut vm,
607610
ids_data,
@@ -674,10 +677,10 @@ mod tests {
674677
/// memory is checked in program_loader.rs tests.
675678
#[rstest]
676679
fn test_load_program(fibonacci: Program) {
677-
let fibonacci_task = Task::Cairo0Program(Cairo0Executable {
680+
let fibonacci_task = Arc::new(Task::Cairo0Program(Cairo0Executable {
678681
program: fibonacci.clone(),
679682
program_input: None,
680-
});
683+
}));
681684

682685
let (
683686
mut vm,
@@ -718,10 +721,10 @@ mod tests {
718721
/// extension, and that the output runner data is set correctly.
719722
#[rstest]
720723
fn test_call_task(fibonacci: Program) {
721-
let fibonacci_task = Task::Cairo0Program(Cairo0Executable {
724+
let fibonacci_task = Arc::new(Task::Cairo0Program(Cairo0Executable {
722725
program: fibonacci.clone(),
723726
program_input: None,
724-
});
727+
}));
725728
let (
726729
mut vm,
727730
ids_data,
@@ -757,12 +760,12 @@ mod tests {
757760
vm.builtin_runners
758761
.push(BuiltinRunner::Output(output_builtin));
759762

760-
let task = Task::Cairo0Program(Cairo0Executable {
763+
let task = Arc::new(Task::Cairo0Program(Cairo0Executable {
761764
program: fibonacci.clone(),
762765
program_input: None,
763-
});
766+
}));
764767

765-
exec_scopes.insert_box(vars::TASK, Box::new(task));
768+
exec_scopes.insert_value(vars::TASK, task);
766769

767770
let hint_data =
768771
HintProcessorData::new_default(String::from(EXECUTE_TASK_CALL_TASK), ids_data);
@@ -825,7 +828,7 @@ mod tests {
825828
/// the VM, to a similar state as in the execute_task function in execute_task.cairo.
826829
#[rstest]
827830
fn test_call_cairo_pie_task(fibonacci_pie: CairoPie) {
828-
let fibonacci_pie_task = Task::Pie(fibonacci_pie.clone());
831+
let fibonacci_pie_task = Arc::new(Task::Pie(fibonacci_pie.clone()));
829832
let (
830833
mut vm,
831834
ids_data,
@@ -874,7 +877,7 @@ mod tests {
874877
vm.builtin_runners
875878
.push(BuiltinRunner::Output(output_builtin));
876879

877-
let task = Task::Pie(fibonacci_pie.clone());
880+
let task = Arc::new(Task::Pie(fibonacci_pie.clone()));
878881
exec_scopes.insert_value(vars::TASK, task);
879882
let bootloader_program = get_simple_bootloader_program();
880883
exec_scopes.insert_value(vars::PROGRAM_DATA_BASE, program_header_ptr.clone());
@@ -974,7 +977,7 @@ mod tests {
974977
let mut exec_scopes = ExecutionScopes::new();
975978

976979
exec_scopes.insert_value(vars::OUTPUT_RUNNER_DATA, Some(output_builtin_state.clone()));
977-
exec_scopes.insert_value(vars::TASK, fibonacci_task);
980+
exec_scopes.insert_value(vars::TASK, Arc::new(fibonacci_task));
978981
exec_scopes.insert_value(vars::FACT_TOPOLOGIES, Vec::<FactTopology>::new());
979982

980983
append_fact_topologies(&mut vm, &mut exec_scopes, &ids_data, &ap_tracking)
@@ -1057,7 +1060,7 @@ mod tests {
10571060
let mut exec_scopes = ExecutionScopes::new();
10581061
let n_builtins = builtin_usage_program.builtins_len();
10591062
exec_scopes.insert_value(vars::N_BUILTINS, n_builtins);
1060-
exec_scopes.insert_value(vars::TASK, task);
1063+
exec_scopes.insert_value(vars::TASK, Arc::new(task));
10611064

10621065
write_return_builtins_hint(&mut vm, &mut exec_scopes, &ids_data, &ap_tracking)
10631066
.expect("Hint failed unexpectedly");

crates/cairo-program-runner-lib/src/hints/load_cairo_pie.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,9 @@ pub fn extract_segment(maybe_relocatable: MaybeRelocatable) -> Result<isize, Rel
160160
///
161161
/// Makes it more convenient to access values in the Cairo PIE memory.
162162
fn build_cairo_pie_memory_map(memory: &CairoPieMemory) -> HashMap<Relocatable, &MaybeRelocatable> {
163-
let mut memory_map: HashMap<Relocatable, &MaybeRelocatable> = HashMap::new();
163+
// Pre-allocate HashMap with known capacity to avoid repeated rehashing.
164+
let mut memory_map: HashMap<Relocatable, &MaybeRelocatable> =
165+
HashMap::with_capacity(memory.0.len());
164166

165167
for ((segment_index, offset), value) in memory.0.iter() {
166168
let address = Relocatable::from((*segment_index as isize, *offset));

crates/cairo-program-runner-lib/src/hints/simple_bootloader_hints.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,13 @@ pub fn setup_run_simple_bootloader_before_task_execution(
6060

6161
/// Implements
6262
/// %{ tasks = simple_bootloader_input.tasks %}
63-
pub fn set_tasks_variable(exec_scopes: &mut ExecutionScopes) -> Result<(), HintError> {
64-
let simple_bootloader_input: &SimpleBootloaderInput =
65-
exec_scopes.get_ref(vars::SIMPLE_BOOTLOADER_INPUT)?;
66-
exec_scopes.insert_value(vars::TASKS, simple_bootloader_input.tasks.clone());
67-
63+
///
64+
/// Note: The TASKS variable is not actually read anywhere in the Rust implementation.
65+
/// Tasks are accessed directly from SIMPLE_BOOTLOADER_INPUT when needed.
66+
/// This function exists only to maintain compatibility with the Cairo hint interface.
67+
pub fn set_tasks_variable(_exec_scopes: &mut ExecutionScopes) -> Result<(), HintError> {
68+
// Intentionally not cloning tasks - the TASKS variable is never read.
69+
// Tasks are accessed directly via SIMPLE_BOOTLOADER_INPUT.tasks when needed.
6870
Ok(())
6971
}
7072

@@ -135,7 +137,8 @@ pub fn set_current_task(
135137
0
136138
};
137139
}
138-
exec_scopes.insert_value(vars::TASK, task.clone());
140+
// Clone the Arc<Task> (cheap reference count increment) instead of cloning the entire Task.
141+
exec_scopes.insert_value(vars::TASK, simple_bootloader_input.tasks[task_id].task.clone());
139142
exec_scopes.insert_value(vars::USE_PREV_HASH, use_prev_hash);
140143
exec_scopes.insert_value(vars::PROGRAM_HASH_FUNCTION, program_hash_function);
141144

crates/cairo-program-runner-lib/src/hints/types.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::collections::HashMap;
22
use std::path::PathBuf;
33
use std::str::FromStr;
4+
use std::sync::Arc;
45

56
use cairo_lang_casm::hints::Hint;
67
use cairo_lang_runner::Arg;
@@ -262,9 +263,11 @@ struct TaskSpecHelper {
262263
user_args_file: Option<PathBuf>,
263264
}
264265

265-
#[derive(Debug, Clone, PartialEq)]
266+
#[derive(Debug, Clone)]
266267
pub struct TaskSpec {
267-
pub task: Task,
268+
/// Task wrapped in Arc to avoid expensive clones when storing in execution scopes.
269+
/// CairoPie tasks can be several GB in size.
270+
pub task: Arc<Task>,
268271
pub program_hash_function: HashFunc,
269272
}
270273

@@ -372,20 +375,26 @@ impl<'de> Deserialize<'de> for TaskSpec {
372375
};
373376

374377
Ok(TaskSpec {
375-
task,
378+
task: Arc::new(task),
376379
program_hash_function: HashFunc::try_from(helper.program_hash_function)
377380
.map_err(|e| D::Error::custom(format!("Invalid program hash function: {e:?}")))?,
378381
})
379382
}
380383
}
381384

385+
impl PartialEq for TaskSpec {
386+
fn eq(&self, other: &Self) -> bool {
387+
*self.task == *other.task && self.program_hash_function == other.program_hash_function
388+
}
389+
}
390+
382391
impl TaskSpec {
383392
/// Retrieves a reference to the `Task` within the `TaskSpec`.
384393
///
385394
/// # Returns
386395
/// A reference to the `task` field, which is either a `Program` or `CairoPie`.
387396
pub fn load_task(&self) -> &Task {
388-
&self.task
397+
&*self.task
389398
}
390399
}
391400

crates/cairo-program-runner-lib/src/test_utils.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::sync::Arc;
2+
13
use crate::hints::types::Task;
24
use crate::hints::vars;
35
use cairo_vm::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData;
@@ -83,15 +85,15 @@ pub fn get_hint_codes_at_pc(hint_extension: &HintExtension, pc: Relocatable) ->
8385
/// The `task` is inserted into the execution scopes. `load_program_hint` should succeed after this
8486
/// function.
8587
pub fn prepare_vm_for_load_program_loading_test(
86-
task: Task,
88+
task: Arc<Task>,
8789
) -> (
8890
VirtualMachine,
8991
std::collections::HashMap<String, HintReference>,
9092
ExecutionScopes,
9193
ApTracking,
9294
usize, // expected_program_data_segment_index
9395
Relocatable, // program_header_ptr
94-
Task,
96+
Arc<Task>,
9597
) {
9698
let mut vm = VirtualMachine::new(false, false);
9799
vm.set_fp(3);

0 commit comments

Comments
 (0)