pub enum MemoryUtilization {
    Lazy,
    WithThreshold(f32),
    Eager,
}
Expand description

LinkedList holds all elements close to each other in a PinnedVec aiming for better cache locality while using thin references rather than wide pointers and to reduce heap allocations.

In order to achieve O(1) time complexity while avoiding smart pointers, remove and pop operations are able to be Lazy. In this case; i.e., when the strategy is set to MemoryReclaimStrategy::Lazy: every pop_back, pop_front or remove method call leaves a gap in the underlying vector. Status of utilization of the underlying vector can be queried using the memory_status method and the gaps can completely be reclaimed by manually calling the memory_reclaim method which has a time complexity of O(n) where n is the length of the underlying vector.

Being able to be lazy, it is possible to define and use different strategies which would be a better fit for different situations:

  • Lazy: memory_reclaim is never called automatically:
    • leads to the cheapest possible pop_back, pop_front or remove operations,
    • however, the utilization of the vector can be low especially when a large number of elements enter and exit the linked list.
    • might be a better fit where keeping the time complexity of these operations at O(1) is important; or when utilization is not expected to drop too low.
  • Eager: every pop_back, pop_front or remove method call is followed by a memory_reclaim call:
    • this strategy keeps the vector without gaps at 100% utilization;
    • however, abovementioned operations require O(n) time complexity;
    • might be a better fit where memory is scarce and more important than the increased time-complexity of these methods.
  • WithThreshold: pop_back, pop_front or remove method call is followed by a memory_reclaim call only if the memory utilization drops below a pre-determined threshold:
    • this strategy is a generalization of Lazy and Eager allowing to select the required threshold level between memory utilization and amortized time complexity of these methods. Note that setting the least memory utilization to a value lower than 1.0 would still least to a constant amortized time complexity.

Variants§

§

Lazy

With Lazy strategy, memory_reclaim is never called automatically:

  • leads to the cheapest possible pop_back, pop_front or remove_at operations,
  • however, the utilization of the vector can be low especially when a large number of elements enter and exit the linked list.
  • might be a better fit where keeping the time complexity of these operations at O(1) is important; or when utilization is not expected to drop too low.

Examples

use orx_linked_list::prelude::*;

let mut list = LinkedList::with_linear_growth(8)
    .with_memory_utilization(MemoryUtilization::Lazy);

// fill list with 4 elements
list.push_back('a');
list.push_back('b');
list.push_back('c');
list.push_back('d');

let util = list.memory_status();
assert_eq!(4, util.num_active_nodes);
assert_eq!(4, util.num_used_nodes);
assert_eq!(1.00, util.utilization());

// remove 1 of 4
_ = list.remove_at(2);
let util = list.memory_status();
assert_eq!(3, util.num_active_nodes);
assert_eq!(4, util.num_used_nodes);
assert_eq!(0.75, util.utilization());

// pop 2 more
_ = list.pop_back();
_ = list.pop_front();
let util = list.memory_status();
assert_eq!(1, util.num_active_nodes);
assert_eq!(4, util.num_used_nodes);
assert_eq!(0.25, util.utilization());

// remove the last element
_ = list.remove_at(0);
let util = list.memory_status();
assert_eq!(0, util.num_active_nodes);
assert_eq!(4, util.num_used_nodes);
assert_eq!(0.00, util.utilization());
§

WithThreshold(f32)

With WithThresholdstrategy, pop_back, pop_front or remove_at method call is followed by a memory_reclaim call only if the memory utilization drops below the pre-determined threshold: * this strategy is a generalization of Lazy and Eager allowing to select the required threshold level between memory utilization and amortized time complexity of these methods. Note that setting the least memory utilization to a value lower than 1.0 would still least to a constant amortized time complexity.

Examples

use orx_linked_list::prelude::*;

let mut list = LinkedList::with_linear_growth(8)
    .with_memory_utilization(MemoryUtilization::WithThreshold(0.51));

// fill list with 4 elements
list.push_back('a');
list.push_back('b');
list.push_back('c');
list.push_back('d');

let util = list.memory_status();
assert_eq!(4, util.num_active_nodes);
assert_eq!(4, util.num_used_nodes);
assert_eq!(1.00, util.utilization());

// remove 1 of 4; utilization remains above the threshold
_ = list.remove_at(2);
let util = list.memory_status();
assert_eq!(3, util.num_active_nodes);
assert_eq!(4, util.num_used_nodes);
assert_eq!(0.75, util.utilization());

// pop 1 more which would reduce utilization to 0.50
// since it is below the treshold; the memory will be reclaimed immediately
_ = list.pop_back();
let util = list.memory_status();
assert_eq!(2, util.num_active_nodes);
assert_eq!(2, util.num_used_nodes);
assert_eq!(1.00, util.utilization());
§

Eager

With Eager strategy, every pop_back, pop_front or remove_at method call is followed by a memory_reclaim call:

  • this strategy keeps the vector without gaps at 100% utilization;
  • however, abovementioned operations require O(n) time complexity;
  • might be a better fit where memory is scarce and more important than the increased time-complexity of these methods.

Examples

use orx_linked_list::prelude::*;

let mut list = LinkedList::with_doubling_growth(8)
    .with_memory_utilization(MemoryUtilization::Eager);

// fill list with 4 elements
list.push_back('a');
list.push_back('b');
list.push_back('c');
list.push_back('d');

let util = list.memory_status();
assert_eq!(4, util.num_active_nodes);
assert_eq!(4, util.num_used_nodes);
assert_eq!(1.00, util.utilization());

// remove 1 of 4
_ = list.remove_at(2);
let util = list.memory_status();
assert_eq!(3, util.num_active_nodes);
assert_eq!(3, util.num_used_nodes);
assert_eq!(1.00, util.utilization());

// pop 2 more
_ = list.pop_back();
_ = list.pop_front();
let util = list.memory_status();
assert_eq!(1, util.num_active_nodes);
assert_eq!(1, util.num_used_nodes);
assert_eq!(1.00, util.utilization());

// remove the last element
_ = list.remove_at(0);
let util = list.memory_status();
assert_eq!(0, util.num_active_nodes);
assert_eq!(0, util.num_used_nodes);
assert_eq!(1.00, util.utilization());

Trait Implementations§

source§

impl Clone for MemoryUtilization

source§

fn clone(&self) -> MemoryUtilization

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for MemoryUtilization

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for MemoryUtilization

source§

fn default() -> Self

Returns the “default value” for a type. Read more
source§

impl PartialEq<MemoryUtilization> for MemoryUtilization

source§

fn eq(&self, other: &MemoryUtilization) -> bool

This method tests for self and other values to be equal, and is used by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
source§

impl Copy for MemoryUtilization

source§

impl StructuralPartialEq for MemoryUtilization

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for Twhere U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> ToOwned for Twhere T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.