Skip to content

Commit bc5a14e

Browse files
OppenfmolettaPedro Fontanapefontana
authored
perf: rework MemoryCell for cache efficiency (#1672)
* perf: rework `MemoryCell` for cache efficiency Store data and metadata inline as a single `[u64; 4]` with `32` bytes alignment, fitting a whole number of cells per cache line to reduce evictions and double sharing and to avoid split line access. Besides performance, an observable change is that now `Memory::get` always returns a `Cow::Owned` variant because the decoding process always creates a new value. * fmt --------- Co-authored-by: fmoletta <[email protected]> Co-authored-by: Pedro Fontana <[email protected]> Co-authored-by: Pedro Fontana <[email protected]> Co-authored-by: Federica <[email protected]>
1 parent f3161e3 commit bc5a14e

File tree

9 files changed

+200
-135
lines changed

9 files changed

+200
-135
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22

33
#### Upcoming Changes
44

5+
* perf: use a more compact representation for `MemoryCell` [#1672](https://github.com/lambdaclass/cairo-vm/pull/1672)
6+
* BREAKING: `Memory::get_value` will now always return `Cow::Owned` variants, code that relied on `Cow::Borrowed` may break
7+
58
#### [1.0.0-rc2] - 2024-05-02
69

10+
711
* `cairo1-run` CLI: Allow loading arguments from file[#1739](https://github.com/lambdaclass/cairo-vm/pull/1739)
812

913
* BREAKING: Remove unused `CairoRunner` field `original_steps`[#1742](https://github.com/lambdaclass/cairo-vm/pull/1742)

vm/src/vm/runners/builtin_runner/ec_op.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::air_private_input::{PrivateInput, PrivateInputEcOp};
2-
use crate::stdlib::{borrow::Cow, prelude::*};
2+
use crate::stdlib::prelude::*;
33
use crate::stdlib::{cell::RefCell, collections::HashMap};
44
use crate::types::instance_definitions::ec_op_instance_def::{
55
CELLS_PER_EC_OP, INPUT_CELLS_PER_EC_OP, SCALAR_HEIGHT,
@@ -122,14 +122,13 @@ impl EcOpBuiltinRunner {
122122

123123
//All input cells should be filled, and be integer values
124124
//If an input cell is not filled, return None
125-
let mut input_cells = Vec::<&Felt252>::with_capacity(INPUT_CELLS_PER_EC_OP as usize);
125+
let mut input_cells = Vec::<Felt252>::with_capacity(INPUT_CELLS_PER_EC_OP as usize);
126126
for i in 0..INPUT_CELLS_PER_EC_OP as usize {
127127
match memory.get(&(instance + i)?) {
128128
None => return Ok(None),
129129
Some(addr) => {
130-
input_cells.push(match addr {
131-
// Only relocatable values can be owned
132-
Cow::Borrowed(MaybeRelocatable::Int(ref num)) => num,
130+
input_cells.push(match addr.as_ref() {
131+
MaybeRelocatable::Int(num) => *num,
133132
_ => {
134133
return Err(RunnerError::Memory(MemoryError::ExpectedInteger(
135134
Box::new((instance + i)?),
@@ -149,21 +148,21 @@ impl EcOpBuiltinRunner {
149148
// Assert that if the current address is part of a point, the point is on the curve
150149
for pair in &EC_POINT_INDICES[0..2] {
151150
if !EcOpBuiltinRunner::point_on_curve(
152-
input_cells[pair.0],
153-
input_cells[pair.1],
151+
&input_cells[pair.0],
152+
&input_cells[pair.1],
154153
&alpha,
155154
&beta,
156155
) {
157156
return Err(RunnerError::PointNotOnCurve(Box::new((
158-
*input_cells[pair.0],
159-
*input_cells[pair.1],
157+
input_cells[pair.0],
158+
input_cells[pair.1],
160159
))));
161160
};
162161
}
163162
let result = EcOpBuiltinRunner::ec_op_impl(
164163
(input_cells[0].to_owned(), input_cells[1].to_owned()),
165164
(input_cells[2].to_owned(), input_cells[3].to_owned()),
166-
input_cells[4],
165+
&input_cells[4],
167166
SCALAR_HEIGHT,
168167
)?;
169168
self.cache.borrow_mut().insert(x_addr, result.0);

vm/src/vm/runners/builtin_runner/mod.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,11 @@ impl BuiltinRunner {
446446
for i in 0..n {
447447
for j in 0..n_input_cells {
448448
let offset = cells_per_instance * i + j;
449-
if let None | Some(None) = builtin_segment.get(offset) {
449+
if builtin_segment
450+
.get(offset)
451+
.filter(|x| x.is_some())
452+
.is_none()
453+
{
450454
missing_offsets.push(offset)
451455
}
452456
}
@@ -463,7 +467,11 @@ impl BuiltinRunner {
463467
for i in 0..n {
464468
for j in n_input_cells..cells_per_instance {
465469
let offset = cells_per_instance * i + j;
466-
if let None | Some(None) = builtin_segment.get(offset) {
470+
if builtin_segment
471+
.get(offset)
472+
.filter(|x| x.is_some())
473+
.is_none()
474+
{
467475
vm.verify_auto_deductions_for_addr(
468476
Relocatable::from((builtin_segment_index as isize, offset)),
469477
self,
@@ -651,6 +659,7 @@ mod tests {
651659
use crate::types::program::Program;
652660
use crate::vm::errors::memory_errors::InsufficientAllocatedCellsError;
653661
use crate::vm::runners::cairo_runner::CairoRunner;
662+
use crate::vm::vm_memory::memory::MemoryCell;
654663
use crate::{utils::test_utils::*, vm::vm_core::VirtualMachine};
655664
use assert_matches::assert_matches;
656665

@@ -1321,7 +1330,7 @@ mod tests {
13211330

13221331
let mut vm = vm!();
13231332

1324-
vm.segments.memory.data = vec![vec![None, None, None]];
1333+
vm.segments.memory.data = vec![vec![MemoryCell::NONE, MemoryCell::NONE, MemoryCell::NONE]];
13251334

13261335
assert_matches!(builtin.run_security_checks(&vm), Ok(()));
13271336
}

vm/src/vm/runners/builtin_runner/range_check.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,7 @@ impl<const N_PARTS: u64> RangeCheckBuiltinRunner<N_PARTS> {
122122
// Split value into n_parts parts of less than _INNER_RC_BOUND size.
123123
for value in range_check_segment {
124124
rc_bounds = value
125-
.as_ref()?
126-
.get_value()
125+
.get_value()?
127126
.get_int_ref()?
128127
.to_le_digits()
129128
// TODO: maybe skip leading zeros
@@ -151,8 +150,8 @@ impl<const N_PARTS: u64> RangeCheckBuiltinRunner<N_PARTS> {
151150
pub fn air_private_input(&self, memory: &Memory) -> Vec<PrivateInput> {
152151
let mut private_inputs = vec![];
153152
if let Some(segment) = memory.data.get(self.base) {
154-
for (index, val) in segment.iter().enumerate() {
155-
if let Some(value) = val.as_ref().and_then(|cell| cell.get_value().get_int()) {
153+
for (index, cell) in segment.iter().enumerate() {
154+
if let Some(value) = cell.get_value().and_then(|value| value.get_int()) {
156155
private_inputs.push(PrivateInput::Value(PrivateInputValue { index, value }))
157156
}
158157
}

vm/src/vm/runners/cairo_runner.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -944,13 +944,13 @@ impl CairoRunner {
944944
self.relocated_memory.push(None);
945945
for (index, segment) in vm.segments.memory.data.iter().enumerate() {
946946
for (seg_offset, cell) in segment.iter().enumerate() {
947-
match cell {
947+
match cell.get_value() {
948948
Some(cell) => {
949949
let relocated_addr = relocate_address(
950950
Relocatable::from((index as isize, seg_offset)),
951951
relocation_table,
952952
)?;
953-
let value = relocate_value(cell.get_value().clone(), relocation_table)?;
953+
let value = relocate_value(cell, relocation_table)?;
954954
if self.relocated_memory.len() <= relocated_addr {
955955
self.relocated_memory.resize(relocated_addr + 1, None);
956956
}
@@ -4098,11 +4098,11 @@ mod tests {
40984098

40994099
vm.segments.memory.data = vec![
41004100
vec![
4101-
Some(MemoryCell::new(Felt252::from(0x8000_8023_8012u64).into())),
4102-
Some(MemoryCell::new(Felt252::from(0xBFFF_8000_0620u64).into())),
4103-
Some(MemoryCell::new(Felt252::from(0x8FFF_8000_0750u64).into())),
4101+
MemoryCell::new(Felt252::from(0x8000_8023_8012u64).into()),
4102+
MemoryCell::new(Felt252::from(0xBFFF_8000_0620u64).into()),
4103+
MemoryCell::new(Felt252::from(0x8FFF_8000_0750u64).into()),
41044104
],
4105-
vec![Some(MemoryCell::new((0isize, 0usize).into())); 128 * 1024],
4105+
vec![MemoryCell::new((0isize, 0usize).into()); 128 * 1024],
41064106
];
41074107

41084108
cairo_runner
@@ -4125,9 +4125,9 @@ mod tests {
41254125
let cairo_runner = cairo_runner!(program);
41264126
let mut vm = vm!();
41274127

4128-
vm.segments.memory.data = vec![vec![Some(MemoryCell::new(mayberelocatable!(
4128+
vm.segments.memory.data = vec![vec![MemoryCell::new(mayberelocatable!(
41294129
0x80FF_8000_0530u64
4130-
)))]];
4130+
))]];
41314131
vm.builtin_runners =
41324132
vec![RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(12), true).into()];
41334133

@@ -4162,9 +4162,9 @@ mod tests {
41624162
let mut vm = vm!();
41634163
vm.builtin_runners = vec![];
41644164
vm.current_step = 10000;
4165-
vm.segments.memory.data = vec![vec![Some(MemoryCell::new(mayberelocatable!(
4165+
vm.segments.memory.data = vec![vec![MemoryCell::new(mayberelocatable!(
41664166
0x80FF_8000_0530u64
4167-
)))]];
4167+
))]];
41684168
vm.trace = Some(vec![TraceEntry {
41694169
pc: (0, 0).into(),
41704170
ap: 0,
@@ -4185,9 +4185,9 @@ mod tests {
41854185
let mut vm = vm!();
41864186
vm.builtin_runners =
41874187
vec![RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(8), true).into()];
4188-
vm.segments.memory.data = vec![vec![Some(MemoryCell::new(mayberelocatable!(
4188+
vm.segments.memory.data = vec![vec![MemoryCell::new(mayberelocatable!(
41894189
0x80FF_8000_0530u64
4190-
)))]];
4190+
))]];
41914191
vm.trace = Some(vec![TraceEntry {
41924192
pc: (0, 0).into(),
41934193
ap: 0,
@@ -4253,9 +4253,9 @@ mod tests {
42534253
let mut vm = vm!();
42544254
vm.builtin_runners =
42554255
vec![RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(8), true).into()];
4256-
vm.segments.memory.data = vec![vec![Some(MemoryCell::new(mayberelocatable!(
4256+
vm.segments.memory.data = vec![vec![MemoryCell::new(mayberelocatable!(
42574257
0x80FF_8000_0530u64
4258-
)))]];
4258+
))]];
42594259
vm.trace = Some(vec![TraceEntry {
42604260
pc: (0, 0).into(),
42614261
ap: 0,
@@ -4750,7 +4750,7 @@ mod tests {
47504750
vm.builtin_runners.push(output_builtin.into());
47514751
vm.segments.memory.data = vec![
47524752
vec![],
4753-
vec![Some(MemoryCell::new(MaybeRelocatable::from((0, 0))))],
4753+
vec![MemoryCell::new(MaybeRelocatable::from((0, 0)))],
47544754
vec![],
47554755
];
47564756
vm.set_ap(1);
@@ -4780,8 +4780,8 @@ mod tests {
47804780
let output_builtin = OutputBuiltinRunner::new(true);
47814781
vm.builtin_runners.push(output_builtin.into());
47824782
vm.segments.memory.data = vec![
4783-
vec![Some(MemoryCell::new(MaybeRelocatable::from((0, 0))))],
4784-
vec![Some(MemoryCell::new(MaybeRelocatable::from((0, 1))))],
4783+
vec![MemoryCell::new(MaybeRelocatable::from((0, 0)))],
4784+
vec![MemoryCell::new(MaybeRelocatable::from((0, 1)))],
47854785
vec![],
47864786
];
47874787
vm.set_ap(1);
@@ -4814,10 +4814,10 @@ mod tests {
48144814
vm.builtin_runners.push(bitwise_builtin.into());
48154815
cairo_runner.initialize_segments(&mut vm, None);
48164816
vm.segments.memory.data = vec![
4817-
vec![Some(MemoryCell::new(MaybeRelocatable::from((0, 0))))],
4817+
vec![MemoryCell::new(MaybeRelocatable::from((0, 0)))],
48184818
vec![
4819-
Some(MemoryCell::new(MaybeRelocatable::from((2, 0)))),
4820-
Some(MemoryCell::new(MaybeRelocatable::from((3, 5)))),
4819+
MemoryCell::new(MaybeRelocatable::from((2, 0))),
4820+
MemoryCell::new(MaybeRelocatable::from((3, 5))),
48214821
],
48224822
vec![],
48234823
];

vm/src/vm/security.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ pub fn verify_secure_runner(
6565
// Asumption: If temporary memory is empty, this means no temporary memory addresses were generated and all addresses in memory are real
6666
if !vm.segments.memory.temp_data.is_empty() {
6767
for value in vm.segments.memory.data.iter().flatten() {
68-
match value.as_ref().map(|x| x.get_value()) {
68+
match value.get_value() {
6969
Some(MaybeRelocatable::RelocatableValue(addr)) if addr.segment_index < 0 => {
7070
return Err(VirtualMachineError::InvalidMemoryValueTemporaryAddress(
71-
Box::new(*addr),
71+
Box::new(addr),
7272
))
7373
}
7474
_ => {}

vm/src/vm/vm_core.rs

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -695,12 +695,12 @@ impl VirtualMachine {
695695
)
696696
.map_err(VirtualMachineError::RunnerError)?
697697
{
698-
let value = value.as_ref().map(|x| x.get_value());
699-
if Some(&deduced_memory_cell) != value && value.is_some() {
698+
let value = value.get_value();
699+
if Some(&deduced_memory_cell) != value.as_ref() && value.is_some() {
700700
return Err(VirtualMachineError::InconsistentAutoDeduction(Box::new((
701701
builtin.name(),
702702
deduced_memory_cell,
703-
value.cloned(),
703+
value,
704704
))));
705705
}
706706
}
@@ -3039,8 +3039,8 @@ mod tests {
30393039
//Check that the following addresses have been accessed:
30403040
// Addresses have been copied from python execution:
30413041
let mem = vm.segments.memory.data;
3042-
assert!(mem[1][0].as_ref().unwrap().is_accessed());
3043-
assert!(mem[1][1].as_ref().unwrap().is_accessed());
3042+
assert!(mem[1][0].is_accessed());
3043+
assert!(mem[1][1].is_accessed());
30443044
}
30453045

30463046
#[test]
@@ -3137,15 +3137,15 @@ mod tests {
31373137
//Check that the following addresses have been accessed:
31383138
// Addresses have been copied from python execution:
31393139
let mem = &vm.segments.memory.data;
3140-
assert!(mem[0][1].as_ref().unwrap().is_accessed());
3141-
assert!(mem[0][4].as_ref().unwrap().is_accessed());
3142-
assert!(mem[0][6].as_ref().unwrap().is_accessed());
3143-
assert!(mem[1][0].as_ref().unwrap().is_accessed());
3144-
assert!(mem[1][1].as_ref().unwrap().is_accessed());
3145-
assert!(mem[1][2].as_ref().unwrap().is_accessed());
3146-
assert!(mem[1][3].as_ref().unwrap().is_accessed());
3147-
assert!(mem[1][4].as_ref().unwrap().is_accessed());
3148-
assert!(mem[1][5].as_ref().unwrap().is_accessed());
3140+
assert!(mem[0][1].is_accessed());
3141+
assert!(mem[0][4].is_accessed());
3142+
assert!(mem[0][6].is_accessed());
3143+
assert!(mem[1][0].is_accessed());
3144+
assert!(mem[1][1].is_accessed());
3145+
assert!(mem[1][2].is_accessed());
3146+
assert!(mem[1][3].is_accessed());
3147+
assert!(mem[1][4].is_accessed());
3148+
assert!(mem[1][5].is_accessed());
31493149
assert_eq!(
31503150
vm.segments
31513151
.memory
@@ -4273,11 +4273,11 @@ mod tests {
42734273
//Check that the following addresses have been accessed:
42744274
// Addresses have been copied from python execution:
42754275
let mem = &vm.segments.memory.data;
4276-
assert!(mem[0][0].as_ref().unwrap().is_accessed());
4277-
assert!(mem[0][1].as_ref().unwrap().is_accessed());
4278-
assert!(mem[0][2].as_ref().unwrap().is_accessed());
4279-
assert!(mem[0][10].as_ref().unwrap().is_accessed());
4280-
assert!(mem[1][1].as_ref().unwrap().is_accessed());
4276+
assert!(mem[0][0].is_accessed());
4277+
assert!(mem[0][1].is_accessed());
4278+
assert!(mem[0][2].is_accessed());
4279+
assert!(mem[0][10].is_accessed());
4280+
assert!(mem[1][1].is_accessed());
42814281
assert_eq!(
42824282
vm.segments
42834283
.memory
@@ -4499,8 +4499,8 @@ mod tests {
44994499
//Check that the following addresses have been accessed:
45004500
// Addresses have been copied from python execution:
45014501
let mem = vm.segments.memory.data;
4502-
assert!(mem[1][0].as_ref().unwrap().is_accessed());
4503-
assert!(mem[1][1].as_ref().unwrap().is_accessed());
4502+
assert!(mem[1][0].is_accessed());
4503+
assert!(mem[1][1].is_accessed());
45044504
}
45054505

45064506
#[test]
@@ -4600,15 +4600,15 @@ mod tests {
46004600
//Check that the following addresses have been accessed:
46014601
// Addresses have been copied from python execution:
46024602
let mem = &vm.segments.memory.data;
4603-
assert!(mem[4][1].as_ref().unwrap().is_accessed());
4604-
assert!(mem[4][4].as_ref().unwrap().is_accessed());
4605-
assert!(mem[4][6].as_ref().unwrap().is_accessed());
4606-
assert!(mem[1][0].as_ref().unwrap().is_accessed());
4607-
assert!(mem[1][1].as_ref().unwrap().is_accessed());
4608-
assert!(mem[1][2].as_ref().unwrap().is_accessed());
4609-
assert!(mem[1][3].as_ref().unwrap().is_accessed());
4610-
assert!(mem[1][4].as_ref().unwrap().is_accessed());
4611-
assert!(mem[1][5].as_ref().unwrap().is_accessed());
4603+
assert!(mem[4][1].is_accessed());
4604+
assert!(mem[4][4].is_accessed());
4605+
assert!(mem[4][6].is_accessed());
4606+
assert!(mem[1][0].is_accessed());
4607+
assert!(mem[1][1].is_accessed());
4608+
assert!(mem[1][2].is_accessed());
4609+
assert!(mem[1][3].is_accessed());
4610+
assert!(mem[1][4].is_accessed());
4611+
assert!(mem[1][5].is_accessed());
46124612
assert_eq!(
46134613
vm.segments
46144614
.memory

0 commit comments

Comments
 (0)