orx_tree/memory.rs
1use crate::{Tree, TreeVariant, pinned_storage::PinnedStorage};
2use orx_selfref_col::{MemoryReclaimNever, MemoryReclaimOnThreshold, MemoryReclaimer, Utilization};
3
4/// Trees use a pinned vector ([`PinnedVec`]) as its underlying storage.
5/// Deletions from the tree are lazy in the sense that the nodes are closed; not removed from the vector.
6/// Therefore, deletions leave a gap in the underlying collection.
7/// How these nodes will be reclaimed is determined by the `MemoryPolicy`.
8///
9/// This is important because during memory reclaim, nodes of the collection are reorganized which
10/// invalidates the node indices which are obtained prior to this operation.
11/// Node indices, or [`NodeIdx`], are analogous to usize indices of a slice and allow for safe direct
12/// constant time access to the node it is created for.
13///
14/// There are three available policies:
15///
16/// * [`Auto`]
17/// * The default policy.
18/// * It automatically reclaims memory whenever the utilization falls below 75%.
19/// * Automatic triggers of memory reclaims might lead to implicit invalidation of node indices due to [`ReorganizedCollection`].
20/// * It is important to note that, even when using Auto policy, tree growth will never trigger memory reclaim.
21/// Only node removing mutations such as [`prune`] can trigger node reorganization.
22/// * [`AutoWithThreshold`]
23/// * This is a generalization of the Auto policy; in particular, Auto is equivalent to `AutoWithThreshold<2>`.
24/// * Its constant parameter `D` defines the utilization threshold to trigger the memory reclaim operation.
25/// * Specifically, memory of closed nodes will be reclaimed whenever the ratio of closed nodes to all nodes exceeds one over `2^D`.
26/// The greater the `D`, the greater the utilization threshold.
27/// * [`Lazy`]
28/// * It never reclaims memory.
29/// * It never leads to implicit invalidation of node indices.
30/// In other words, a node index can only be invalidated if we prune that node from the tree ([`RemovedNode`]).
31///
32/// An ideal use pattern can conveniently be achieved by using auto and lazy policies together,
33/// using the free memory policy transformation methods.
34/// Such a use case is demonstrated in the example below.
35///
36/// [`PinnedVec`]: orx_pinned_vec::PinnedVec
37/// [`NodeIdx`]: crate::NodeIdx
38/// [`prune`]: crate::NodeMut::prune
39/// [`ReorganizedCollection`]: crate::NodeIdxError::ReorganizedCollection
40/// [`RemovedNode`]: crate::NodeIdxError::RemovedNode
41///
42/// # Examples
43///
44/// ## Auto Memory Claim
45///
46/// Since Auto is the default memory policy, we do not need to include it in the tree type signature.
47/// The following example demonstrates how this policy impacts the validity of node indices.
48///
49/// The follow up example will demonstrate how to switch between the [`Lazy`] and [`Auto`] policies
50/// to make best use of these policies and use both node indices and memory efficiently.
51///
52/// ```
53/// use orx_tree::*;
54///
55/// // # 1 - BUILD UP
56///
57/// // 1
58/// // ╱ ╲
59/// // ╱ ╲
60/// // 2 3
61/// // ╱ ╲ ╱ ╲
62/// // 4 5 6 7
63/// // | | ╱ ╲
64/// // 8 9 10 11
65///
66/// fn bfs_values(tree: &DynTree<i32>) -> Vec<i32> {
67/// tree.root().walk::<Bfs>().copied().collect()
68/// }
69///
70/// // # 1. GROW
71///
72/// // equivalently => DynTree::<i32, Auto>::new(1)
73/// let mut tree = DynTree::new(1);
74///
75/// let [id2, id3] = tree.root_mut().push_children([2, 3]);
76/// let [id4, _] = tree.node_mut(&id2).push_children([4, 5]);
77/// let [id8] = tree.node_mut(&id4).push_children([8]);
78/// let [id6, id7] = tree.node_mut(&id3).push_children([6, 7]);
79/// tree.node_mut(&id6).push_child(9);
80/// tree.node_mut(&id7).push_children([10, 11]);
81///
82/// assert_eq!(bfs_values(&tree), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
83///
84/// // all id's above are valid => the tree only grew
85/// assert!(tree.is_node_idx_valid(&id2)); // is_valid_for => true
86/// assert!(tree.get_node(&id4).is_some()); // get_node => Some(Node)
87/// assert!(tree.try_node(&id6).is_ok()); // try_get_node => Ok(Node)
88/// let _node7 = tree.node(&id7); // no panic
89///
90/// // # 2 - SHRINK BUT WITHOUT A MEMORY RECLAIM
91///
92/// // let's close two nodes (nodes 4 & 8)
93/// // this is not enough to trigger a memory reclaim
94/// tree.node_mut(&id4).prune();
95/// assert_eq!(bfs_values(&tree), [1, 2, 3, 5, 6, 7, 9, 10, 11]);
96///
97/// assert!(tree.is_node_idx_valid(&id2)); // is_valid_for => true
98/// assert!(tree.try_node(&id6).is_ok()); // try_get_node => Ok(Node)
99/// let node7 = tree.node(&id7); // no panic
100///
101/// // what about id4 & id8 => invalidated due to RemovedNode
102/// assert!(!tree.is_node_idx_valid(&id4));
103/// assert!(tree.get_node(&id4).is_none());
104/// assert_eq!(tree.try_node(&id4), Err(NodeIdxError::RemovedNode));
105/// // let node4 = id4.node(&tree); // panics!!!
106///
107/// // # 3 - SHRINK TRIGGERING MEMORY RECLAIM
108///
109/// // let's close more nodes (7, 10, 11) to trigger the memory reclaim
110/// tree.node_mut(&id7).prune();
111/// assert_eq!(bfs_values(&tree), [1, 2, 3, 5, 6, 9]);
112///
113/// // even node 2 is still on the tree;
114/// // its idx is invalidated => ReorganizedCollection
115/// assert!(!tree.is_node_idx_valid(&id2));
116/// assert_eq!(
117/// tree.try_node(&id2),
118/// Err(NodeIdxError::ReorganizedCollection)
119/// );
120///
121/// // all indices obtained prior to reorganization are now invalid
122/// // we can restore the valid indices again
123///
124/// let id2 = tree.root().get_child(0).unwrap().idx();
125/// assert!(tree.is_node_idx_valid(&id2));
126/// assert!(tree.try_node(&id2).is_ok());
127/// let n2 = tree.node(&id2);
128/// assert_eq!(n2.data(), &2);
129/// ```
130///
131/// ## Lazy Memory Claim: Preventing Invalid Indices
132///
133/// Now assume that we want to make sure that the indices we cached during growth
134/// are valid during the stages #2 and #3.
135/// We can achieve this by switching to Lazy policy and only after stage #3,
136/// when we are done using the indices, we can switch back to Auto policy.
137/// Note that these transformations of the memory policies are free.
138///
139/// ```
140/// use orx_tree::*;
141///
142/// // # 1 - BUILD UP
143///
144/// // 1
145/// // ╱ ╲
146/// // ╱ ╲
147/// // 2 3
148/// // ╱ ╲ ╱ ╲
149/// // 4 5 6 7
150/// // | | ╱ ╲
151/// // 8 9 10 11
152///
153/// fn bfs_values<M: MemoryPolicy>(tree: &DynTree<i32, M>) -> Vec<i32> {
154/// tree.root().walk::<Bfs>().copied().collect()
155/// }
156///
157/// // # 1. GROW
158///
159/// // or just => DynTree::<i32, Lazy>::new(1);
160/// let mut tree = DynTree::new(1).into_lazy_reclaim();
161///
162/// let [id2, id3] = tree.root_mut().push_children([2, 3]);
163/// let [id4, _] = tree.node_mut(&id2).push_children([4, 5]);
164/// let [id8] = tree.node_mut(&id4).push_children([8]);
165/// let [id6, id7] = tree.node_mut(&id3).push_children([6, 7]);
166/// tree.node_mut(&id6).push_child(9);
167/// tree.node_mut(&id7).push_children([10, 11]);
168///
169/// assert_eq!(bfs_values(&tree), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
170///
171/// // all id's above are valid => we are in Lazy mode & the tree only grew
172/// assert!(tree.is_node_idx_valid(&id2)); // is_valid_for => true
173/// assert!(tree.get_node(&id4).is_some()); // get_node => Some(Node)
174/// assert!(tree.try_node(&id6).is_ok()); // try_get_node => Ok(Node)
175/// let _node7 = tree.node(&id7); // no panic!
176///
177/// // # 2 - SHRINK, NO MEMORY RECLAIM
178///
179/// // let's close two nodes (nodes 4 & 8)
180/// tree.node_mut(&id4).prune();
181/// assert_eq!(bfs_values(&tree), [1, 2, 3, 5, 6, 7, 9, 10, 11]);
182///
183/// assert!(tree.is_node_idx_valid(&id2)); // is_valid_for => true
184/// assert!(tree.try_node(&id6).is_ok()); // try_get_node => Ok(Node)
185/// let node7 = tree.node(&id7); // no panic
186///
187/// // only id4 & id8 are affected (explicit) => invalidated due to RemovedNode
188/// assert!(!tree.is_node_idx_valid(&id4));
189/// assert!(tree.get_node(&id4).is_none());
190/// assert_eq!(tree.try_node(&id4), Err(NodeIdxError::RemovedNode));
191/// // let node4 = id4.node(&tree); // panics!
192///
193/// // # 3 - SHRINK HEAVILY, STILL NO MEMORY RECLAIM
194///
195/// // let's close more nodes (7, 10, 11)
196/// // this would've triggered memory reclaim in Auto policy, but not in Lazy policy
197/// tree.node_mut(&id7).prune();
198/// assert_eq!(bfs_values(&tree), [1, 2, 3, 5, 6, 9]);
199///
200/// // all indices are still valid ✓
201/// assert!(tree.is_node_idx_valid(&id2));
202/// assert!(tree.try_node(&id2).is_ok());
203/// let n2 = tree.node(&id2);
204/// assert_eq!(n2.data(), &2);
205///
206/// // # 4 - END OF HEAVY MUTATIONS, RECLAIM THE MEMORY
207///
208/// // since utilization was below 75% (6 active nodes / 11)
209/// // memory is reclaimed immediately once switch to Auto.
210/// // now, all prior indices are invalid
211/// let tree: DynTree<i32, Auto> = tree.into_auto_reclaim();
212/// assert!(!tree.is_node_idx_valid(&id2));
213/// assert!(tree.get_node(&id3).is_none());
214/// assert_eq!(
215/// tree.try_node(&id4),
216/// Err(NodeIdxError::ReorganizedCollection)
217/// );
218/// ```
219pub trait MemoryPolicy: 'static {
220 /// Memory reclaim policy for the specific tree variant `V`.
221 type MemoryReclaimPolicy<V>: orx_selfref_col::MemoryPolicy<V>
222 where
223 V: TreeVariant;
224}
225
226/// The `Lazy` policy never reclaims closed nodes.
227///
228/// * It never reclaims memory.
229/// * It never leads to implicit invalidation of node indices.
230/// In other words, a node index can only be invalidated if we remove that node from the tree ([`RemovedNode`]).
231///
232/// Compared to `Auto`, lazy approach has the following pros & cons:
233///
234/// * (+) Node indices ([`NodeIdx<V>`]) created for a lazy tree will never be implicitly invalidated.
235/// Recall that, analogous to random access to an array element with its index,
236/// node indices allow for constant time access to the corresponding node.
237/// A node index can be invalidated if either of the following happens
238/// (i) the node is removed from the tree,
239/// (ii) other nodes are removed from the tree triggering a memory reclaim operation.
240/// With lazy policy, the second case can never happen.
241/// * (-) Uses more memory due to the gaps of the closed nodes especially when there exist many deletions
242/// from the tree.
243///
244/// Notice that the con can never be observed for grow-only situations; or might be insignificant when the
245/// number of removals is not very large. In such situations, it is the recommended memory reclaim policy.
246///
247/// [`NodeIdx<V>`]: orx_selfref_col::NodeIdx
248/// [`RemovedNode`]: crate::NodeIdxError::RemovedNode
249pub struct Lazy;
250impl MemoryPolicy for Lazy {
251 type MemoryReclaimPolicy<V>
252 = MemoryReclaimNever
253 where
254 V: TreeVariant;
255}
256
257/// The `Auto` policy reclaims closed nodes whenever the utilization falls below 75%.
258///
259/// * The default policy; and hence, the generic parameter can be omitted.
260/// * It automatically reclaims memory whenever the utilization falls below 75%.
261/// * Automatic triggers of memory reclaims might lead to implicit invalidation of node indices due to [`ReorganizedCollection`].
262/// * It is important to note that, even when using Auto policy, tree growth will never trigger memory reclaim.
263/// Only node removing mutations such as [`prune`] can trigger node reorganization.
264///
265/// Compared to `Lazy`, auto approach has the following pros & cons:
266///
267/// * (+) Node indices ([`NodeIdx<V>`]) created for an auto-reclaim tree will can be implicitly invalidated.
268/// Recall that, analogous to random access to an array element with its index,
269/// node indices allow for constant time access to the corresponding node.
270/// A node index can be invalidated if either of the following happens
271/// (i) the node is removed from the tree,
272/// (ii) other nodes are removed from the tree triggering a memory reclaim operation.
273/// With auto policy, both the explicit (i) and implicit (ii) invalidation can occur.
274/// * (-) Uses memory efficiently making sure that utilization can never go below a constant threshold.
275///
276/// [`NodeIdx<V>`]: orx_selfref_col::NodeIdx
277///
278/// Since Auto is the default memory policy, we do not need to include it in the tree type signature.
279///
280/// In order to observe the impact of the memory reclaim policy on validity of the node indices,
281/// please see the Examples section of [`MemoryPolicy`].
282///
283/// [`ReorganizedCollection`]: crate::NodeIdxError::ReorganizedCollection
284/// [`prune`]: crate::NodeMut::prune
285pub struct Auto;
286impl MemoryPolicy for Auto {
287 type MemoryReclaimPolicy<V>
288 = MemoryReclaimOnThreshold<2, V, V::Reclaimer>
289 where
290 V: TreeVariant;
291}
292
293/// The `AutoWithThreshold` policy reclaims closed nodes whenever the utilization falls below a certain threshold
294/// which is determined by the constant parameter `D`.
295///
296/// * This is a generalization of the [`Auto`] policy; in particular, Auto is equivalent to `AutoWithThreshold<2>`.
297/// * Its constant parameter `D` defines the utilization threshold to trigger the memory reclaim operation.
298/// * Specifically, memory of closed nodes will be reclaimed whenever the ratio of closed nodes to all nodes exceeds one over `2^D`.
299/// The greater the `D`, the greater the utilization threshold.
300/// * when `D = 0`: memory will be reclaimed when utilization is below 0.00% (equivalent to never).
301/// * when `D = 1`: memory will be reclaimed when utilization is below 50.00%.
302/// * when `D = 2`: memory will be reclaimed when utilization is below 75.00%.
303/// * when `D = 3`: memory will be reclaimed when utilization is below 87.50%.
304/// * when `D = 4`: memory will be reclaimed when utilization is below 93.75%.
305///
306/// Compared to `Lazy`, auto approach has the following pros & cons:
307///
308/// * (+) Node indices ([`NodeIdx<V>`]) created for an auto-reclaim tree will can be implicitly invalidated.
309/// Recall that, analogous to random access to an array element with its index,
310/// node indices allow for constant time access to the corresponding node.
311/// A node index can be invalidated if either of the following happens
312/// (i) the node is removed from the tree,
313/// (ii) other nodes are removed from the tree triggering a memory reclaim operation.
314/// With auto policy, both the explicit (i) and implicit (ii) invalidation can occur.
315/// * (-) Uses memory efficiently making sure that utilization can never go below a constant threshold.
316///
317/// [`NodeIdx<V>`]: orx_selfref_col::NodeIdx
318///
319/// Since Auto is the default memory policy, we do not need to include it in the tree type signature.
320///
321/// In order to observe the impact of the memory reclaim policy on validity of the node indices,
322/// please see the Examples section of [`MemoryPolicy`].
323pub struct AutoWithThreshold<const D: usize>;
324impl<const D: usize> MemoryPolicy for AutoWithThreshold<D> {
325 type MemoryReclaimPolicy<V>
326 = MemoryReclaimOnThreshold<D, V, V::Reclaimer>
327 where
328 V: TreeVariant;
329}
330
331// tree methods
332
333impl<V, M, P> Tree<V, M, P>
334where
335 V: TreeVariant,
336 M: MemoryPolicy,
337 P: PinnedStorage,
338{
339 /// Returns current node utilization of the collection.
340 pub fn memory_utilization(&self) -> Utilization {
341 self.0.utilization()
342 }
343}
344
345// tree memory policy transformations: into_lazy_reclaim
346
347impl<V, P> Tree<V, Auto, P>
348where
349 V: TreeVariant,
350 P: PinnedStorage,
351{
352 /// Transforms the tree's memory reclaim policy from [`Auto`] to [`Lazy`]:
353 ///
354 /// * `Tree<V>` => `Tree<V, Lazy>` // Auto can be omitted on since it is the default
355 /// * `DynTree<u62>` => `DynTree<u32, Lazy>`
356 /// * `BinaryTree<u62>` => `BinaryTree<u32, Lazy>`
357 ///
358 /// This is free operation and does not require any allocation or copies.
359 pub fn into_lazy_reclaim(self) -> Tree<V, Lazy, P> {
360 Tree(self.0.into())
361 }
362}
363
364impl<const D: usize, V, P> Tree<V, AutoWithThreshold<D>, P>
365where
366 V: TreeVariant,
367 P: PinnedStorage,
368{
369 /// Transforms the tree's memory reclaim policy from [`AutoWithThreshold`] to [`Lazy`]:
370 ///
371 /// * `Tree<V, AutoWithThreshold<3>>` => `Tree<V, Lazy>`
372 /// * `DynTree<u62, AutoWithThreshold<4>>` => `DynTree<u32, Lazy>`
373 /// * `BinaryTree<u62, AutoWithThreshold<1>>` => `BinaryTree<u32, Lazy>`
374 ///
375 /// This is free operation and does not require any allocation or copies.
376 pub fn into_lazy_reclaim(self) -> Tree<V, Lazy, P> {
377 Tree(self.0.into())
378 }
379}
380
381// tree memory policy transformations: into_auto_reclaim
382
383impl<V, P> Tree<V, Lazy, P>
384where
385 V: TreeVariant,
386 P: PinnedStorage,
387{
388 /// Transforms the tree's memory reclaim policy from [`Lazy`] to [`Auto`]:
389 ///
390 /// * `Tree<V, Lazy>` => `Tree<V>` // Auto can be omitted on since it is the default
391 /// * `DynTree<u62, Lazy>` => `DynTree<u32>`
392 /// * `BinaryTree<u62, Lazy>` => `BinaryTree<u32>`
393 ///
394 /// This is free operation and does not require any allocation or copies.
395 pub fn into_auto_reclaim(self) -> Tree<V, Auto, P> {
396 let mut tree = Tree(self.0.into());
397 let will_reclaim =
398 <Auto as MemoryPolicy>::MemoryReclaimPolicy::<V>::col_needs_memory_reclaim(&tree.0);
399
400 if will_reclaim {
401 let nodes_moved = <V::Reclaimer as MemoryReclaimer<V>>::reclaim_nodes(&mut tree.0);
402 tree.0.update_state(nodes_moved);
403 }
404 tree
405 }
406
407 /// Transforms the tree's memory reclaim policy from [`Lazy`] to [`AutoWithThreshold`]:
408 ///
409 /// * `Tree<V, Lazy>` => `Tree<V, AutoWithThreshold<3>>`
410 /// * `DynTree<u62, Lazy>` => `DynTree<u32, AutoWithThreshold<1>>`
411 /// * `BinaryTree<u62, Lazy>` => `BinaryTree<u32, AutoWithThreshold<4>>`
412 ///
413 /// This is free operation and does not require any allocation or copies.
414 pub fn into_auto_reclaim_with_threshold<const D: usize>(
415 self,
416 ) -> Tree<V, AutoWithThreshold<D>, P> {
417 let mut tree = Tree(self.0.into());
418 let will_reclaim =
419 <AutoWithThreshold<D> as MemoryPolicy>::MemoryReclaimPolicy::<V>::col_needs_memory_reclaim(&tree.0);
420 if will_reclaim {
421 <V::Reclaimer as MemoryReclaimer<V>>::reclaim_nodes(&mut tree.0);
422 }
423 tree
424 }
425}