Skip to content

Commit 2ea41a3

Browse files
refactor: remove expensive clones from verify_consistency_of_operations; convert API to slice
There was never any need for this function to take in a &Vec; we should never do that. Instead take in a slice. Additionally, we refactored some of the loop logic to use .take instead of a check on each iteration to see if we were at the end. We also combined two duplicate for loops into 1. Finally, we significantly refactor the return type to avoid a lot of very expensive cloning. Previously, we were doing something on the magnitude of 9 clones (of expensive underlying objects including vectors) per input GroveDbOp. All of these have been removed. We instead return references to these values instead of clones of them. This does mean that the lifetime of the return value is tied to the underlying data the slice refers to; but especially as I cannot even find an instance where we use these return values, this feels like a very fair trade off.
1 parent d007bed commit 2ea41a3

File tree

1 file changed

+49
-55
lines changed

1 file changed

+49
-55
lines changed

grovedb/src/batch/mod.rs

Lines changed: 49 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -552,95 +552,87 @@ impl GroveDbOp {
552552
}
553553

554554
/// Verify consistency of operations
555-
pub fn verify_consistency_of_operations(ops: &Vec<GroveDbOp>) -> GroveDbOpConsistencyResults {
556-
let ops_len = ops.len();
555+
pub fn verify_consistency_of_operations(ops: &[GroveDbOp]) -> GroveDbOpConsistencyResults {
557556
// operations should not have any duplicates
558557
let mut repeated_ops = vec![];
559-
for (i, op) in ops.iter().enumerate() {
560-
if i == ops_len {
561-
continue;
562-
} // Don't do last one
558+
let mut same_path_key_ops = vec![];
559+
// Exclude the last item
560+
for (i, op) in ops.iter().take(ops.len() - 1).enumerate() {
563561
let count = ops
564562
.split_at(i + 1)
565563
.1
566564
.iter()
567565
.filter(|&current_op| current_op == op)
568566
.count() as u16;
569567
if count > 1 {
570-
repeated_ops.push((op.clone(), count));
568+
repeated_ops.push((op, count));
571569
}
572-
}
573570

574-
let mut same_path_key_ops = vec![];
575-
576-
// No double insert or delete of same key in same path
577-
for (i, op) in ops.iter().enumerate() {
578-
if i == ops_len {
579-
continue;
580-
} // Don't do last one
571+
// No double insert or delete of same key in same path
581572
let mut doubled_ops = ops
582573
.split_at(i + 1)
583574
.1
584575
.iter()
585576
.filter_map(|current_op| {
586577
if current_op.path == op.path && current_op.key == op.key {
587-
Some(current_op.op.clone())
578+
Some(&current_op.op)
588579
} else {
589580
None
590581
}
591582
})
592-
.collect::<Vec<Op>>();
583+
.collect::<Vec<&Op>>();
593584
if !doubled_ops.is_empty() {
594-
doubled_ops.push(op.op.clone());
595-
same_path_key_ops.push((op.path.clone(), op.key.clone(), doubled_ops));
585+
doubled_ops.push(&op.op);
586+
same_path_key_ops.push((&op.path, &op.key, doubled_ops));
596587
}
597588
}
598589

599590
let inserts = ops
600591
.iter()
601592
.filter_map(|current_op| match current_op.op {
602-
Op::Insert { .. } | Op::Replace { .. } => Some(current_op.clone()),
593+
Op::Insert { .. } | Op::Replace { .. } => Some(current_op),
603594
_ => None,
604595
})
605-
.collect::<Vec<GroveDbOp>>();
596+
.collect::<Vec<&GroveDbOp>>();
606597

607598
let deletes = ops
608599
.iter()
609600
.filter_map(|current_op| {
610601
if let Op::Delete = current_op.op {
611-
Some(current_op.clone())
602+
Some(current_op)
612603
} else {
613604
None
614605
}
615606
})
616-
.collect::<Vec<GroveDbOp>>();
617-
618-
let mut insert_ops_below_deleted_ops = vec![];
607+
.collect::<Vec<&GroveDbOp>>();
619608

620-
// No inserts under a deleted path
621-
for deleted_op in deletes.iter() {
622-
let mut deleted_qualified_path = deleted_op.path.clone();
623-
deleted_qualified_path.push(deleted_op.key.clone());
624-
let inserts_with_deleted_ops_above = inserts
625-
.iter()
626-
.filter_map(|inserted_op| {
627-
if deleted_op.path.len() < inserted_op.path.len()
628-
&& deleted_qualified_path
629-
.iterator()
630-
.zip(inserted_op.path.iterator())
631-
.all(|(a, b)| a == b)
632-
{
633-
Some(inserted_op.clone())
634-
} else {
635-
None
636-
}
637-
})
638-
.collect::<Vec<GroveDbOp>>();
639-
if !inserts_with_deleted_ops_above.is_empty() {
640-
insert_ops_below_deleted_ops
641-
.push((deleted_op.clone(), inserts_with_deleted_ops_above));
642-
}
643-
}
609+
let insert_ops_below_deleted_ops = deletes
610+
.iter()
611+
.filter_map(|&deleted_op| {
612+
let mut deleted_qualified_path = deleted_op.path.clone();
613+
deleted_qualified_path.push(deleted_op.key.clone());
614+
let inserts_with_deleted_ops_above = inserts
615+
.iter()
616+
.filter_map(|&inserted_op| {
617+
if deleted_op.path.len() < inserted_op.path.len()
618+
&& deleted_qualified_path
619+
.iterator()
620+
.zip(inserted_op.path.iterator())
621+
.all(|(a, b)| a == b)
622+
{
623+
Some(inserted_op)
624+
} else {
625+
None
626+
}
627+
})
628+
.collect::<Vec<&GroveDbOp>>();
629+
if !inserts_with_deleted_ops_above.is_empty() {
630+
Some((deleted_op, inserts_with_deleted_ops_above))
631+
} else {
632+
None
633+
}
634+
})
635+
.collect::<Vec<(&GroveDbOp, Vec<&GroveDbOp>)>>();
644636

645637
GroveDbOpConsistencyResults {
646638
repeated_ops,
@@ -652,14 +644,16 @@ impl GroveDbOp {
652644

653645
/// Results of a consistency check on an operation batch
654646
#[derive(Debug)]
655-
pub struct GroveDbOpConsistencyResults {
656-
repeated_ops: Vec<(GroveDbOp, u16)>, // the u16 is count
657-
same_path_key_ops: Vec<(KeyInfoPath, KeyInfo, Vec<Op>)>,
658-
insert_ops_below_deleted_ops: Vec<(GroveDbOp, Vec<GroveDbOp>)>, /* the deleted op first,
659-
* then inserts under */
647+
pub struct GroveDbOpConsistencyResults<'a> {
648+
repeated_ops: Vec<(&'a GroveDbOp, u16)>, // the u16 is count
649+
same_path_key_ops: Vec<(&'a KeyInfoPath, &'a KeyInfo, Vec<&'a Op>)>,
650+
insert_ops_below_deleted_ops: Vec<(&'a GroveDbOp, Vec<&'a GroveDbOp>)>, /* the deleted op
651+
* first,
652+
* then inserts
653+
* under */
660654
}
661655

662-
impl GroveDbOpConsistencyResults {
656+
impl GroveDbOpConsistencyResults<'_> {
663657
/// Check if results are empty
664658
pub fn is_empty(&self) -> bool {
665659
self.repeated_ops.is_empty()

0 commit comments

Comments
 (0)