linkrs_storage/edge/mutable_csr/
compaction.rs1use super::super::csr_shared::is_reclaimable_cold;
2use super::super::{ColdStamps, EdgeId, HotNbr, Nbr, Timestamp};
3use super::overflow::{OverflowChunk, OverflowStorage};
4use super::row::PACKED_CSR_DENSITY;
5use super::MutableCsr;
6
7impl MutableCsr {
8 pub fn compact_with_ts_reporting(
19 &mut self,
20 cutoff: Timestamp,
21 reserve_ratio: f32,
22 on_edge_removed: &mut dyn FnMut(EdgeId, Timestamp),
23 ) -> usize {
24 let removals_enabled = cutoff < Timestamp::MAX;
26 let rows = self.vertex_capacity();
27
28 let mut new_degrees = Vec::with_capacity(rows);
30 let mut new_capacities = Vec::with_capacity(rows);
31 let mut removed_count = 0usize;
32 for vid in 0..rows {
33 let (hot, cold) = self.primary_pair(vid);
34 let mut kept = 0usize;
35 for (h, c) in hot.iter().zip(cold.iter()) {
36 if removals_enabled && is_reclaimable_cold(c, cutoff) {
37 on_edge_removed(h.edge_id, c.delete_ts);
38 removed_count += 1;
39 } else {
40 kept += 1;
41 }
42 }
43 if let Some(chunks) = self.overflow_chunks.get(vid as u32) {
44 for chunk in chunks {
45 for (hot, cold) in chunk.hot_slice().iter().zip(chunk.cold_slice()) {
46 if removals_enabled && is_reclaimable_cold(cold, cutoff) {
47 on_edge_removed(hot.edge_id, cold.delete_ts);
48 removed_count += 1;
49 } else {
50 kept += 1;
51 }
52 }
53 }
54 }
55 new_degrees.push(kept as u32);
56 new_capacities.push(Self::compact_row_capacity(kept, reserve_ratio) as u32);
57 }
58
59 let new_total_edge_capacity: usize = new_capacities.iter().map(|&c| c as usize).sum();
61 let mut new_hot_list = Vec::with_capacity(new_total_edge_capacity);
62 let mut new_cold_list = Vec::with_capacity(new_total_edge_capacity);
63 let mut final_offsets = Vec::with_capacity(rows);
64 for vid in 0..rows {
65 final_offsets.push(new_hot_list.len() as u32);
66 let (hot, cold) = self.primary_pair(vid);
67 for (h, c) in hot.iter().zip(cold.iter()) {
68 if removals_enabled && is_reclaimable_cold(c, cutoff) {
69 continue;
70 }
71 new_hot_list.push(*h);
72 new_cold_list.push(*c);
73 }
74 if let Some(chunks) = self.overflow_chunks.get(vid as u32) {
75 for chunk in chunks {
76 for (hot, cold) in chunk.hot_slice().iter().zip(chunk.cold_slice()) {
77 if removals_enabled && is_reclaimable_cold(cold, cutoff) {
78 continue;
79 }
80 new_hot_list.push(*hot);
81 new_cold_list.push(*cold);
82 }
83 }
84 }
85 let cap = new_capacities[vid] as usize;
88 let deg = new_degrees[vid] as usize;
89 debug_assert_eq!(new_hot_list.len() - final_offsets[vid] as usize, deg);
90 let remaining = cap - deg;
91 if remaining > 0 {
92 new_hot_list.resize(new_hot_list.len() + remaining, HotNbr::dead_gap());
93 new_cold_list.resize(new_cold_list.len() + remaining, ColdStamps::dead_gap());
94 }
95 }
96
97 self.hot_list = new_hot_list;
98 self.cold_list = new_cold_list;
99 self.rows.adj_offsets = final_offsets;
100 self.rows.degrees = new_degrees;
101 self.rows.primary_capacities = new_capacities;
102 self.total_edge_capacity = new_total_edge_capacity;
103
104 self.overflow_chunks = OverflowStorage::new();
105 self.live_sets.clear();
106 self.rebuild_live_sets();
107 self.reset_reuse_hints();
108 self.reset_primary_sorted();
109
110 removed_count
111 }
112
113 pub(crate) fn compact_row_capacity(kept: usize, reserve_ratio: f32) -> usize {
122 if kept == 0 {
123 return 0;
124 }
125 if reserve_ratio < 1.0 {
126 let density = 1.0 - reserve_ratio;
127 if (density - PACKED_CSR_DENSITY).abs() < 1e-6 {
128 return Self::sized_row_capacity(kept);
129 }
130 return ((kept as f32 / density).ceil() as usize).max(1);
131 }
132 kept.max(1)
133 }
134
135 pub fn reclaimable_count(&self, vid: u32, cutoff: Timestamp) -> usize {
140 if cutoff == Timestamp::MAX {
141 return 0;
142 }
143 let idx = vid as usize;
144 if idx >= self.vertex_capacity() {
145 return 0;
146 }
147 let mut count = 0;
148 let (_, cold) = self.primary_pair(idx);
149 for c in cold.iter() {
150 if is_reclaimable_cold(c, cutoff) {
151 count += 1;
152 }
153 }
154 if let Some(chunks) = self.overflow_chunks.get(vid) {
155 for chunk in chunks {
156 for cold in chunk.cold_slice() {
157 if is_reclaimable_cold(cold, cutoff) {
158 count += 1;
159 }
160 }
161 }
162 }
163 count
164 }
165
166 pub fn vertex_needs_compact(&self, vid: u32, cutoff: Timestamp) -> bool {
168 self.reclaimable_count(vid, cutoff) > 0
169 }
170
171 pub fn vertex_reclaim_probe(&self, vid: u32, cutoff: Timestamp) -> (usize, usize) {
178 let idx = vid as usize;
179 if idx >= self.vertex_capacity() {
180 return (0, 0);
181 }
182 let eligible = cutoff != Timestamp::MAX;
183 let mut dead = 0usize;
184 let mut reclaimable = 0usize;
185 let (_, cold) = self.primary_pair(idx);
186 for c in cold.iter() {
187 if !c.is_live() {
188 dead += 1;
189 if eligible && is_reclaimable_cold(c, cutoff) {
190 reclaimable += 1;
191 }
192 }
193 }
194 if let Some(chunks) = self.overflow_chunks.get(vid) {
195 for chunk in chunks {
196 for cold in chunk.cold_slice() {
197 if !cold.is_live() {
198 dead += 1;
199 if eligible && is_reclaimable_cold(cold, cutoff) {
200 reclaimable += 1;
201 }
202 }
203 }
204 }
205 }
206 (dead, reclaimable)
207 }
208
209 pub fn vertex_census(&self, vid: u32) -> (usize, usize, usize) {
215 let idx = vid as usize;
216 if idx >= self.vertex_capacity() {
217 return (0, 0, 0);
218 }
219 let mut live = 0usize;
220 let mut dead = 0usize;
221 let (_, cold) = self.primary_pair(idx);
222 for c in cold.iter() {
223 if c.is_live() {
224 live += 1;
225 } else {
226 dead += 1;
227 }
228 }
229 let mut capacity = self.rows.primary_capacities[idx] as usize;
230 if let Some(chunks) = self.overflow_chunks.get(vid) {
231 capacity += chunks.iter().map(|chunk| chunk.capacity()).sum::<usize>();
232 for chunk in chunks {
233 for cold in chunk.cold_slice() {
234 if cold.is_live() {
235 live += 1;
236 } else {
237 dead += 1;
238 }
239 }
240 }
241 }
242 (live, dead, capacity)
243 }
244
245 pub fn compact_vertex_with_reporting(
253 &mut self,
254 vid: u32,
255 cutoff: Timestamp,
256 on_edge_removed: &mut dyn FnMut(EdgeId, Timestamp),
257 ) -> usize {
258 if cutoff == Timestamp::MAX {
259 return 0;
260 }
261 let idx = vid as usize;
262 if idx >= self.vertex_capacity() {
263 return 0;
264 }
265 if self.vertex_reclaim_probe(vid, cutoff).1 == 0 {
269 return 0;
270 }
271 let mut removed = 0usize;
272
273 let degree = self.rows.degrees[idx] as usize;
274 let offset = self.rows.adj_offsets[idx] as usize;
275 let mut keep = 0usize;
276 for i in 0..degree {
277 let drop = self
278 .cold_at(offset + i)
279 .is_some_and(|cold| is_reclaimable_cold(&cold, cutoff));
280 if drop {
281 let hot = self.hot_list[offset + i];
282 let cold = self.cold_list[offset + i];
283 on_edge_removed(hot.edge_id, cold.delete_ts);
284 removed += 1;
285 } else {
286 if keep != i {
287 let kept_slot = self.slot_at(offset + i).unwrap();
288 self.set_slot(offset + keep, kept_slot);
289 }
290 keep += 1;
291 }
292 }
293 self.rows.degrees[idx] = keep as u32;
294 for slot in offset + keep..offset + degree {
298 self.hot_list[slot] = HotNbr::dead_gap();
299 self.cold_list[slot] = ColdStamps::dead_gap();
300 }
301 self.invalidate_reuse_hint(idx);
302
303 if self.overflow_chunks.get(vid).is_some() {
304 let chunks = self.overflow_chunks.remove(vid).unwrap_or_default();
308 let freed: usize = chunks.iter().map(|chunk| chunk.capacity()).sum();
309 self.sub_capacity(freed);
310 let mut kept: Vec<Nbr> = Vec::new();
311 for chunk in &chunks {
312 for i in 0..chunk.len() {
313 let nbr = chunk.slot_at(i).unwrap();
314 if is_reclaimable_cold(&nbr.cold(), cutoff) {
315 on_edge_removed(nbr.edge_id, nbr.delete_ts);
316 removed += 1;
317 } else {
318 kept.push(nbr);
319 }
320 }
321 }
322 if kept.is_empty() {
323 self.rebuild_live_set_for_vertex(vid);
324 } else {
325 let single = OverflowChunk::consolidated(&kept);
327 let added: usize = single.capacity();
328 self.add_capacity(added);
329 self.overflow_chunks.insert(vid, vec![single]);
330 self.rebuild_live_set_for_vertex(vid);
331 }
332 } else if removed > 0 {
333 self.rebuild_live_set_for_vertex(vid);
334 }
335
336 removed
337 }
338}