linkrs_storage/edge/mutable_csr/
row.rs1use super::super::csr_shared::is_reclaimable_cold;
2use super::super::{EdgeId, Nbr, Timestamp};
3use super::overflow::OverflowChunk;
4use super::MutableCsr;
5
6pub(crate) const PACKED_CSR_DENSITY: f32 = 0.8;
17pub(crate) const MIN_ROW_CAPACITY: usize = 4;
25
26pub(crate) const OVERFLOW_CHUNK_MIN: usize = 8;
40pub(crate) const OVERFLOW_CHUNK_MAX: usize = 4096;
41
42pub(crate) fn graded_overflow_chunk_edges(live: usize) -> usize {
46 live.next_power_of_two()
47 .clamp(OVERFLOW_CHUNK_MIN, OVERFLOW_CHUNK_MAX)
48}
49
50pub(crate) const HIGH_DEGREE_LIVE_THRESHOLD: usize = 16_384;
60pub(crate) const HIGH_DEGREE_CHUNK_EDGES: usize = 16_384;
67pub(crate) const HIGH_DEGREE_SPARE_DIVISOR: usize = 4;
71
72impl MutableCsr {
73 pub(crate) fn sized_row_capacity(live: usize) -> usize {
75 if live == 0 {
76 return 0;
77 }
78 ((live as f32 / PACKED_CSR_DENSITY).ceil() as usize).max(MIN_ROW_CAPACITY.min(live.max(1)))
79 }
80
81 pub(crate) fn effective_chunk_edges(&self, live: usize) -> usize {
83 if live >= HIGH_DEGREE_LIVE_THRESHOLD {
84 return graded_overflow_chunk_edges(live)
85 .max(HIGH_DEGREE_CHUNK_EDGES)
86 .max(1);
87 }
88 graded_overflow_chunk_edges(live)
89 .min(self.overflow_chunk_edges)
90 .max(1)
91 }
92
93 pub fn row_gap(&self, vid: u32) -> usize {
95 let idx = vid as usize;
96 if idx >= self.vertex_capacity() {
97 return 0;
98 }
99 let (_, cold) = self.primary_pair(idx);
100 let live = cold.iter().filter(|c| c.is_live()).count();
101 (self.rows.primary_capacities[idx] as usize).saturating_sub(live)
102 }
103
104 pub fn row_density(&self, vid: u32) -> f32 {
106 let idx = vid as usize;
107 if idx >= self.vertex_capacity() {
108 return 1.0;
109 }
110 let cap = self.rows.primary_capacities[idx] as usize;
111 if cap == 0 {
112 return 1.0;
113 }
114 let (_, cold) = self.primary_pair(idx);
115 let live = cold.iter().filter(|c| c.is_live()).count();
116 live as f32 / cap as f32
117 }
118
119 pub fn rebalance_row(&mut self, vid: u32) -> bool {
126 let idx = vid as usize;
127 if idx >= self.vertex_capacity() || self.rows.primary_capacities[idx] == 0 {
128 return false;
129 }
130 if self.overflow_chunks.get(vid).is_none_or(Vec::is_empty) {
131 return false;
132 }
133 let degree = self.rows.degrees[idx] as usize;
134 let offset = self.rows.adj_offsets[idx] as usize;
135 let cap = self.rows.primary_capacities[idx] as usize;
136 let mut live: Vec<Nbr> = Vec::new();
137 let mut pinned: Vec<Nbr> = Vec::new();
138 for i in 0..degree {
139 if let Some(nbr) = self.slot_at(offset + i) {
140 if nbr.delete_ts == Timestamp::MAX {
141 live.push(nbr);
142 } else {
143 pinned.push(nbr);
144 }
145 }
146 }
147 if let Some(chunks) = self.overflow_chunks.get(vid) {
148 for chunk in chunks {
149 for i in 0..chunk.len() {
150 if let Some(nbr) = chunk.slot_at(i) {
151 if nbr.delete_ts == Timestamp::MAX {
152 live.push(nbr);
153 } else {
154 pinned.push(nbr);
155 }
156 }
157 }
158 }
159 }
160 let slots = cap.min(live.len() + pinned.len());
161 let mut placed = 0usize;
162 for nbr in live.iter().chain(pinned.iter()).take(slots) {
163 if offset + placed < self.hot_list.len() {
164 self.set_slot(offset + placed, *nbr);
165 }
166 placed += 1;
167 }
168 self.rows.degrees[idx] = placed as u32;
169 self.invalidate_reuse_hint(idx);
170 self.mark_primary_unsorted(idx);
171 let placed_live = live.len().min(slots);
172 let overflow_live: Vec<Nbr> = live.into_iter().skip(placed_live).collect();
173 let placed_pinned = pinned.len().min(slots.saturating_sub(placed_live));
174 let overflow_pinned: Vec<Nbr> = pinned.into_iter().skip(placed_pinned).collect();
175 let old_overflow_cap: usize = self.overflow_chunks.get(vid).map_or(0, |chunks| {
176 chunks.iter().map(|chunk| chunk.capacity()).sum()
177 });
178 if overflow_live.is_empty() && overflow_pinned.is_empty() {
179 self.overflow_chunks.remove(vid);
180 self.sub_capacity(old_overflow_cap);
181 } else {
182 let mut rest: Vec<Nbr> =
183 Vec::with_capacity(overflow_live.len() + overflow_pinned.len());
184 rest.extend_from_slice(&overflow_live);
185 rest.extend_from_slice(&overflow_pinned);
186 let single = if rest.len() >= HIGH_DEGREE_LIVE_THRESHOLD {
190 let spare =
191 (rest.len() / HIGH_DEGREE_SPARE_DIVISOR).max(HIGH_DEGREE_CHUNK_EDGES / 2);
192 OverflowChunk::consolidated_with_reserve(&rest, spare)
193 } else {
194 OverflowChunk::consolidated(&rest)
195 };
196 let new_overflow_cap = single.capacity();
197 self.sub_capacity(old_overflow_cap);
198 self.add_capacity(new_overflow_cap);
199 if let Some(slot) = self.overflow_chunks.get_mut(vid) {
200 *slot = vec![single];
201 }
202 }
203 self.rebuild_live_set_for_vertex(vid);
204 self.overflow_chunks.get(vid).is_none_or(Vec::is_empty)
205 }
206
207 pub fn sort_row(&mut self, vid: u32) -> bool {
220 let idx = vid as usize;
221 if idx >= self.vertex_capacity() || self.rows.primary_capacities[idx] == 0 {
222 self.mark_primary_sorted(idx);
223 return false;
224 }
225 let degree = self.rows.degrees[idx] as usize;
226 if degree <= 1 {
227 self.mark_primary_sorted(idx);
228 return false;
229 }
230 let base = self.rows.adj_offsets[idx] as usize;
231 if base.saturating_add(degree) > self.hot_list.len()
232 || base.saturating_add(degree) > self.cold_list.len()
233 {
234 return false;
235 }
236 let mut order: Vec<usize> = (0..degree).collect();
237 let hot = &self.hot_list[base..base + degree];
238 let already = order.windows(2).all(|w| {
239 let a = &hot[w[0]];
240 let b = &hot[w[1]];
241 (a.endpoint, a.rank, a.edge_id.0) <= (b.endpoint, b.rank, b.edge_id.0)
242 });
243 if already {
244 self.mark_primary_sorted(idx);
245 return false;
246 }
247 order.sort_by(|&a, &b| {
248 let ha = &hot[a];
249 let hb = &hot[b];
250 (ha.endpoint, ha.rank, ha.edge_id.0).cmp(&(hb.endpoint, hb.rank, hb.edge_id.0))
251 });
252 let hot_src = self.hot_list[base..base + degree].to_vec();
253 let cold_src = self.cold_list[base..base + degree].to_vec();
254 for (dst, src) in order.iter().enumerate() {
255 self.hot_list[base + dst] = hot_src[*src];
256 self.cold_list[base + dst] = cold_src[*src];
257 }
258 self.invalidate_reuse_hint(idx);
259 self.mark_primary_sorted(idx);
260 self.rebuild_live_set_for_vertex(vid);
261 true
262 }
263
264 pub fn sort_all_rows(&mut self) -> usize {
268 let rows = self.vertex_capacity();
269 let mut reordered = 0usize;
270 for vid in 0..rows {
271 if self.sort_row(vid as u32) {
272 reordered += 1;
273 }
274 }
275 reordered
276 }
277
278 pub(crate) fn compact_overflow_for_vertex(
279 &mut self,
280 vid: u32,
281 cutoff: Timestamp,
282 on_edge_removed: &mut dyn FnMut(EdgeId, Timestamp),
283 ) {
284 let Some(chunks) = self.overflow_chunks.remove(vid) else {
285 return;
286 };
287 let old_cap: usize = chunks.iter().map(|c| c.capacity()).sum();
288 self.sub_capacity(old_cap);
289 let removals_enabled = cutoff != Timestamp::MAX;
290 let mut kept: Vec<Nbr> = Vec::new();
291 for chunk in &chunks {
292 for i in 0..chunk.len() {
293 let nbr = chunk.slot_at(i).unwrap();
294 if removals_enabled && is_reclaimable_cold(&nbr.cold(), cutoff) {
295 on_edge_removed(nbr.edge_id, nbr.delete_ts);
296 } else {
297 kept.push(nbr);
298 }
299 }
300 }
301 if kept.is_empty() {
302 self.rebuild_live_set_for_vertex(vid);
303 return;
304 }
305 let single = if kept.len() >= HIGH_DEGREE_LIVE_THRESHOLD {
310 let spare = (kept.len() / HIGH_DEGREE_SPARE_DIVISOR).max(HIGH_DEGREE_CHUNK_EDGES / 2);
311 OverflowChunk::consolidated_with_reserve(&kept, spare)
312 } else {
313 OverflowChunk::consolidated(&kept)
314 };
315 let new_cap = single.capacity();
316 self.add_capacity(new_cap);
317 self.overflow_chunks.insert(vid, vec![single]);
318 self.rebuild_live_set_for_vertex(vid);
319 }
320}