From 8043bd379a39671ae4123756ca9b4780bf8ecd43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Rze=C5=BAnicki?= Date: Mon, 10 Aug 2026 16:30:18 +0200 Subject: [PATCH 1/2] zipper_algebra: further specialize grafting This leads to a few percent speed-up in the benchmarks --- src/experimental/zipper_algebra.rs | 61 ++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/src/experimental/zipper_algebra.rs b/src/experimental/zipper_algebra.rs index 0c4e4b71..6d20df0c 100644 --- a/src/experimental/zipper_algebra.rs +++ b/src/experimental/zipper_algebra.rs @@ -515,13 +515,11 @@ where Out: ZipperWriting, { if *lhs_grafts != ByteMask::EMPTY { - out.graft_masked_branches(lhs, *lhs_grafts, false); - *lhs_grafts = ByteMask::EMPTY; + do_graft(out, lhs, std::mem::take(lhs_grafts)); } if *rhs_grafts != ByteMask::EMPTY { - out.graft_masked_branches(rhs, *rhs_grafts, false); - *rhs_grafts = ByteMask::EMPTY; + do_graft(out, rhs, std::mem::take(rhs_grafts)); } } @@ -731,18 +729,15 @@ where Out: ZipperWriting, { if *lhs_grafts != ByteMask::EMPTY { - out.graft_masked_branches(lhs, *lhs_grafts, false); - *lhs_grafts = ByteMask::EMPTY; + do_graft(out, lhs, std::mem::take(lhs_grafts)); } if *mid_grafts != ByteMask::EMPTY { - out.graft_masked_branches(mid, *mid_grafts, false); - *mid_grafts = ByteMask::EMPTY; + do_graft(out, mid, std::mem::take(mid_grafts)); } if *rhs_grafts != ByteMask::EMPTY { - out.graft_masked_branches(rhs, *rhs_grafts, false); - *rhs_grafts = ByteMask::EMPTY; + do_graft(out, rhs, std::mem::take(rhs_grafts)); } } @@ -1016,23 +1011,19 @@ fn zipper_merge4( Out: ZipperWriting, { if *z0_grafts != ByteMask::EMPTY { - out.graft_masked_branches(z0, *z0_grafts, false); - *z0_grafts = ByteMask::EMPTY; + do_graft(out, z0, std::mem::take(z0_grafts)); } if *z1_grafts != ByteMask::EMPTY { - out.graft_masked_branches(z1, *z1_grafts, false); - *z1_grafts = ByteMask::EMPTY; + do_graft(out, z1, std::mem::take(z1_grafts)); } if *z2_grafts != ByteMask::EMPTY { - out.graft_masked_branches(z2, *z2_grafts, false); - *z2_grafts = ByteMask::EMPTY; + do_graft(out, z2, std::mem::take(z2_grafts)); } if *z3_grafts != ByteMask::EMPTY { - out.graft_masked_branches(z3, *z3_grafts, false); - *z3_grafts = ByteMask::EMPTY; + do_graft(out, z3, std::mem::take(z3_grafts)); } } @@ -1631,6 +1622,37 @@ fn with_k( f(refs) } +fn do_graft(dst: &mut Out, src: &Z, grafts: ByteMask) +where + V: Clone + Send + Sync, + A: Allocator, + Out: ZipperWriting, + Z: ZipperInfallibleSubtries, +{ + match grafts.count_bits() { + 0 => {} + 1 => { + let byte = grafts.indexed_bit::(0).expect("one bit set"); + dst.descend_to_byte(byte); + dst.graft_src_at(src, &[byte]); + dst.ascend_byte(); + } + 2 => { + let first_byte = grafts.indexed_bit::(0).expect("some bit set"); + dst.descend_to_byte(first_byte); + dst.graft_src_at(src, &[first_byte]); + dst.ascend_byte(); + let second_byte = grafts.next_bit(first_byte).expect("two bits set"); + dst.descend_to_byte(second_byte); + dst.graft_src_at(src, &[second_byte]); + dst.ascend_byte(); + } + _ => { + dst.graft_masked_branches(src, grafts, false); + } + } +} + // - The function is fully monomorphized over `Z` and `N` and uses a bitmask (`active`) // to track participating zippers. // - Small frontiers (`k ≤ 4`) are dispatched to specialized implementations @@ -1674,8 +1696,7 @@ where { for_each_bit(active, |i| { if grafts[i] != ByteMask::EMPTY { - out.graft_masked_branches(&zs[i], grafts[i], false); - grafts[i] = ByteMask::EMPTY; + do_graft(out, &zs[i], std::mem::take(&mut grafts[i])); } }); } From 353f4d43f937956f87bf518d2b05eba7aa5ab333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Rze=C5=BAnicki?= Date: Sun, 16 Aug 2026 22:55:22 +0200 Subject: [PATCH 2/2] Fold graft optimization into the masked version --- src/experimental/zipper_algebra.rs | 51 +++-------- src/write_zipper.rs | 140 ++++++++++++++++++----------- 2 files changed, 97 insertions(+), 94 deletions(-) diff --git a/src/experimental/zipper_algebra.rs b/src/experimental/zipper_algebra.rs index 6d20df0c..19634e2c 100644 --- a/src/experimental/zipper_algebra.rs +++ b/src/experimental/zipper_algebra.rs @@ -515,11 +515,11 @@ where Out: ZipperWriting, { if *lhs_grafts != ByteMask::EMPTY { - do_graft(out, lhs, std::mem::take(lhs_grafts)); + out.graft_masked_branches(lhs, std::mem::take(lhs_grafts), false); } if *rhs_grafts != ByteMask::EMPTY { - do_graft(out, rhs, std::mem::take(rhs_grafts)); + out.graft_masked_branches(rhs, std::mem::take(rhs_grafts), false); } } @@ -729,15 +729,15 @@ where Out: ZipperWriting, { if *lhs_grafts != ByteMask::EMPTY { - do_graft(out, lhs, std::mem::take(lhs_grafts)); + out.graft_masked_branches(lhs, std::mem::take(lhs_grafts), false); } if *mid_grafts != ByteMask::EMPTY { - do_graft(out, mid, std::mem::take(mid_grafts)); + out.graft_masked_branches(mid, std::mem::take(mid_grafts), false); } if *rhs_grafts != ByteMask::EMPTY { - do_graft(out, rhs, std::mem::take(rhs_grafts)); + out.graft_masked_branches(rhs, std::mem::take(rhs_grafts), false); } } @@ -1011,19 +1011,19 @@ fn zipper_merge4( Out: ZipperWriting, { if *z0_grafts != ByteMask::EMPTY { - do_graft(out, z0, std::mem::take(z0_grafts)); + out.graft_masked_branches(z0, std::mem::take(z0_grafts), false); } if *z1_grafts != ByteMask::EMPTY { - do_graft(out, z1, std::mem::take(z1_grafts)); + out.graft_masked_branches(z1, std::mem::take(z1_grafts), false); } if *z2_grafts != ByteMask::EMPTY { - do_graft(out, z2, std::mem::take(z2_grafts)); + out.graft_masked_branches(z2, std::mem::take(z2_grafts), false); } if *z3_grafts != ByteMask::EMPTY { - do_graft(out, z3, std::mem::take(z3_grafts)); + out.graft_masked_branches(z3, std::mem::take(z3_grafts), false); } } @@ -1622,37 +1622,6 @@ fn with_k( f(refs) } -fn do_graft(dst: &mut Out, src: &Z, grafts: ByteMask) -where - V: Clone + Send + Sync, - A: Allocator, - Out: ZipperWriting, - Z: ZipperInfallibleSubtries, -{ - match grafts.count_bits() { - 0 => {} - 1 => { - let byte = grafts.indexed_bit::(0).expect("one bit set"); - dst.descend_to_byte(byte); - dst.graft_src_at(src, &[byte]); - dst.ascend_byte(); - } - 2 => { - let first_byte = grafts.indexed_bit::(0).expect("some bit set"); - dst.descend_to_byte(first_byte); - dst.graft_src_at(src, &[first_byte]); - dst.ascend_byte(); - let second_byte = grafts.next_bit(first_byte).expect("two bits set"); - dst.descend_to_byte(second_byte); - dst.graft_src_at(src, &[second_byte]); - dst.ascend_byte(); - } - _ => { - dst.graft_masked_branches(src, grafts, false); - } - } -} - // - The function is fully monomorphized over `Z` and `N` and uses a bitmask (`active`) // to track participating zippers. // - Small frontiers (`k ≤ 4`) are dispatched to specialized implementations @@ -1696,7 +1665,7 @@ where { for_each_bit(active, |i| { if grafts[i] != ByteMask::EMPTY { - do_graft(out, &zs[i], std::mem::take(&mut grafts[i])); + out.graft_masked_branches(&zs[i], std::mem::take(&mut grafts[i]), false); } }); } diff --git a/src/write_zipper.rs b/src/write_zipper.rs index b12df255..5dfbd5df 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -1501,66 +1501,100 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC } /// See [ZipperWriting::graft_masked_branches] pub fn graft_masked_branches>(&mut self, src: &Z, child_mask: ByteMask, remove_unset: bool) { - match src.get_focus().try_as_tagged() { - Some(src_tagged) => { - // Split the focus if we're in the middle of another node - let self_focus_node = match self.try_borrow_focus_mut() { - Some(node) => node, - None => { - self.split_at_focus(); - self.try_borrow_focus_mut().unwrap() - } - }; - match src_tagged { - TaggedNodeRef::DenseByteNode(src_node) => { - if remove_unset { - Self::merge_branches_into_focus::, true>(self_focus_node, src_node, child_mask); - } else { - Self::merge_branches_into_focus::, false>(self_focus_node, src_node, child_mask); - } - }, - TaggedNodeRef::CellByteNode(src_node) => { - if remove_unset { - Self::merge_branches_into_focus::, true>(self_focus_node, src_node, child_mask); - } else { - Self::merge_branches_into_focus::, false>(self_focus_node, src_node, child_mask); - } - }, - TaggedNodeRef::LineListNode(src_node) => { - let mut src_node = src_node.clone(); - let src_dense = src_node.convert_to_dense::>(3); - let src_dense = src_dense.as_tagged().as_dense().unwrap(); - if remove_unset { - Self::merge_branches_into_focus::, true>(self_focus_node, src_dense, child_mask); - } else { - Self::merge_branches_into_focus::, false>(self_focus_node, src_dense, child_mask); - } - }, - TaggedNodeRef::TinyRefNode(src_node) => { - let mut src_node = src_node.into_full().unwrap(); - let src_dense = src_node.convert_to_dense::>(3); - let src_dense = src_dense.as_tagged().as_dense().unwrap(); - if remove_unset { - Self::merge_branches_into_focus::, true>(self_focus_node, src_dense, child_mask); - } else { - Self::merge_branches_into_focus::, false>(self_focus_node, src_dense, child_mask); + match child_mask.count_bits() { + 0 => { + if remove_unset { + self.remove_branches(false); + } + } + 1 => { + if remove_unset { + self.remove_branches(false); + } + + let byte = child_mask.indexed_bit::(0).expect("one bit set"); + self.descend_to_byte(byte); + self.graft_src_at(src, &[byte]); + self.ascend_byte(); + } + 2 => { + if remove_unset { + self.remove_branches(false); + } + + let first_byte = child_mask.indexed_bit::(0).expect("some bit set"); + self.descend_to_byte(first_byte); + self.graft_src_at(src, &[first_byte]); + self.ascend_byte(); + + let second_byte = child_mask.next_bit(first_byte).expect("two bits set"); + self.descend_to_byte(second_byte); + self.graft_src_at(src, &[second_byte]); + self.ascend_byte(); + } + _ => { + match src.get_focus().try_as_tagged() { + Some(src_tagged) => { + // Split the focus if we're in the middle of another node + let self_focus_node = match self.try_borrow_focus_mut() { + Some(node) => node, + None => { + self.split_at_focus(); + self.try_borrow_focus_mut().unwrap() + } + }; + match src_tagged { + TaggedNodeRef::DenseByteNode(src_node) => { + if remove_unset { + Self::merge_branches_into_focus::, true>(self_focus_node, src_node, child_mask); + } else { + Self::merge_branches_into_focus::, false>(self_focus_node, src_node, child_mask); + } + }, + TaggedNodeRef::CellByteNode(src_node) => { + if remove_unset { + Self::merge_branches_into_focus::, true>(self_focus_node, src_node, child_mask); + } else { + Self::merge_branches_into_focus::, false>(self_focus_node, src_node, child_mask); + } + }, + TaggedNodeRef::LineListNode(src_node) => { + let mut src_node = src_node.clone(); + let src_dense = src_node.convert_to_dense::>(3); + let src_dense = src_dense.as_tagged().as_dense().unwrap(); + if remove_unset { + Self::merge_branches_into_focus::, true>(self_focus_node, src_dense, child_mask); + } else { + Self::merge_branches_into_focus::, false>(self_focus_node, src_dense, child_mask); + } + }, + TaggedNodeRef::TinyRefNode(src_node) => { + let mut src_node = src_node.into_full().unwrap(); + let src_dense = src_node.convert_to_dense::>(3); + let src_dense = src_dense.as_tagged().as_dense().unwrap(); + if remove_unset { + Self::merge_branches_into_focus::, true>(self_focus_node, src_dense, child_mask); + } else { + Self::merge_branches_into_focus::, false>(self_focus_node, src_dense, child_mask); + } + }, + TaggedNodeRef::EmptyNode => { + if remove_unset { + self.remove_branches(false); + } else { + self.remove_unmasked_branches(child_mask.not(), false); + } + }, } }, - TaggedNodeRef::EmptyNode => { + None => { + debug_assert_eq!(src.child_count(), 0); if remove_unset { self.remove_branches(false); } else { self.remove_unmasked_branches(child_mask.not(), false); } - }, - } - }, - None => { - debug_assert_eq!(src.child_count(), 0); - if remove_unset { - self.remove_branches(false); - } else { - self.remove_unmasked_branches(child_mask.not(), false); + } } } }