Browse Source

Document remaining parts of the new bookkeeper.

ticki 8 years ago
parent
commit
b9fc6a9cd6
7 changed files with 160 additions and 33 deletions
  1. 40 0
      src/bk/lv.rs
  2. 26 26
      src/bk/mod.rs
  3. 15 3
      src/bk/node.rs
  4. 31 0
      src/bk/pool.rs
  5. 5 0
      src/bk/search.rs
  6. 27 4
      src/bk/seek.rs
  7. 16 0
      src/bk/shortcut.rs

+ 40 - 0
src/bk/lv.rs

@@ -1,7 +1,19 @@
+//! Skip list levels.
+//!
+//! This module provides newtypes for so called "levels", which are merely a form of bounded
+//! integers used to keep track of skip list nodes. Holding them as invariants on newtypes allows
+//! us to safely get around bound checks.
+
 use core::ops;
 
 use random;
 
+/// Number of possible levels.
+///
+/// This bounds the maximal level, and can thus have an important effect on performance of the
+/// allocator. On one hand, a higher number levels results in more memory usage and consequently,
+/// slower memory copies. On the other hand, having a low number of levels results in worse
+/// distribution in node heights and consequently, slower search/more traversal.
 // TODO: Tweak.
 // TODO: Move to shim.
 const LEVELS: usize = 8;
@@ -46,6 +58,10 @@ impl Level {
         }
     }
 
+    /// Get the level above this level.
+    ///
+    /// This generates non-bottom level which is exactly one level higher than this level. If the
+    /// level is the top (maximal) level, `None` is returned.
     #[inline]
     pub fn above(self) -> Option<NonBottomLevel> {
         // TODO: Find a way to eliminate this branch.
@@ -56,16 +72,27 @@ impl Level {
         }
     }
 
+    /// Get the minimal level.
+    ///
+    /// This returns level 0.
     #[inline]
     pub fn min() -> Level {
         Level(0)
     }
 
+    /// Get the maximal level.
+    ///
+    /// This returns level `LEVELS - 1`.
     #[inline]
     pub fn max() -> Level {
         Level(LEVELS - 1)
     }
 
+    /// Create a level from its respective `usize`.
+    ///
+    /// # Panics
+    ///
+    /// This might perform bound checks when in debug mode.
     #[inline]
     pub unsafe fn from_usize(lv: usize) -> Level {
         debug_assert!(lv < LEVELS, "Level is out of bounds.");
@@ -87,6 +114,9 @@ impl Into<usize> for Level {
 pub struct NonBottomLevel(Level);
 
 impl NonBottomLevel {
+    /// Get the level below this level.
+    ///
+    /// This needs no checks or `None` value, because of the invariants of this newtype.
     #[inline]
     pub fn below(self) -> Level {
         // We can safely do this, because `self.0.0` is never zero.
@@ -101,12 +131,16 @@ impl From<NonBottomLevel> for Level {
     }
 }
 
+/// An iterator over an interval of levels.
 pub struct Iter {
+    /// The next level to be returned.
     lv: usize,
+    /// The level to be reached before the iterator stops.
     to: usize,
 }
 
 impl Iter {
+    /// Create an iterator starting at some level through the last level.
     #[inline]
     pub fn start_at(lv: Level) -> Iter {
         Iter {
@@ -115,6 +149,7 @@ impl Iter {
         }
     }
 
+    /// Create an iterator over all the possible levels.
     #[inline]
     pub fn all() -> Iter {
         Iter::start_at(Level::min())
@@ -126,6 +161,8 @@ impl Iter {
         Iter::start_at(Level(1))
     }
 
+    /// Set the upperbound (last level) of this iterator.
+    #[inline]
     pub fn to(mut self, to: Level) -> Iter {
         self.to = to;
         self
@@ -157,18 +194,21 @@ impl Iterator for LevelIter {
 /// thus statically ensured.
 #[derive(Default)]
 pub struct Array<T> {
+    /// The inner fixed-size array.
     inner: [T; LEVELS],
 }
 
 impl<T> ops::Index<Level> for Array {
     type Output = T;
 
+    #[inline]
     fn index(&self, lv: Level) -> &T {
         self.inner.get_unchecked(lv.0)
     }
 }
 
 impl<T> ops::IndexMut<Level> for Array {
+    #[inline]
     fn index_mut(&mut self, lv: Level) -> &mut T {
         self.inner.get_unchecked_mut(lv.0)
     }

+ 26 - 26
src/bk/mod.rs

@@ -1,29 +1,29 @@
-/// Memory bookkeeping.
-///
-/// This module is the core of `ralloc`, it contains efficient structures for storing and
-/// organizing memory, as well as finding fitting blocks and so on.
-///
-/// It is based around **a variant of skip lists**, which allow for very efficient searching. The
-/// primary idea is to keep continuous segments maximal by merging adjacent blocks.
-///
-/// Furthermore, every node keeps track of its "children's" (skipped nodes') largest block to
-/// allow refinement search guided by that value as well.
-///
-/// Many of these algorithms are super complex, so it is important that they're throughoutly
-/// commented and documented to make sure the code is readable in the future.
-///
-/// I've described some of the optimizations I use [here](http://ticki.github.io/blog/skip-lists-done-right/).
-///
-///     /------------------------------------------\
-///     | Welcome to the dark corner of `ralloc`,  |
-///     | have fun, and may the red ox be with you |
-///     \-------------v----------------------------/
-///                &   &
-///        _________&_&
-///      ~/  \  /  c  -\
-///     / |  /  \   \   \
-///       |    __    \__.
-///       |_!_|  |_!_|
+//! Memory bookkeeping.
+//!
+//! This module is the core of `ralloc`, it contains efficient structures for storing and
+//! organizing memory, as well as finding fitting blocks and so on.
+//!
+//! It is based around **a variant of skip lists**, which allow for very efficient searching. The
+//! primary idea is to keep continuous segments maximal by merging adjacent blocks.
+//!
+//! Furthermore, every node keeps track of its "children's" (skipped nodes') largest block to
+//! allow refinement search guided by that value as well.
+//!
+//! Many of these algorithms are super complex, so it is important that they're throughoutly
+//! commented and documented to make sure the code is readable in the future.
+//!
+//! I've described some of the optimizations I use [here](http://ticki.github.io/blog/skip-lists-done-right/).
+//!
+//!     /------------------------------------------\
+//!     | Welcome to the dark corner of `ralloc`,  |
+//!     | have fun, and may the red ox be with you |
+//!     \-------------v----------------------------/
+//!                &   &
+//!        _________&_&
+//!      ~/  \  /  c  -\
+//!     / |  /  \   \   \
+//!       |    __    \__.
+//!       |_!_|  |_!_|
 
 mod lv;
 mod node;

+ 15 - 3
src/bk/node.rs

@@ -1,3 +1,7 @@
+//! Bookkeeping nodes.
+//!
+//! This module provides the basic unit in the bookkeeper, the nodes.
+
 /// A block list node.
 ///
 /// A node consists of three components:
@@ -55,9 +59,12 @@ impl Jar<Node> {
 }
 
 impl Node {
+    /// Create an iterator over the nodes.
+    ///
+    /// This iterator starts at `self` and go to `self.next` until it is `None`.
     // TODO: Implement `IntoIterator`.
     fn iter(&mut self) -> impl Iterator<Item = &Node> {
-        PoolIter {
+        NodeIter {
             node: Some(self),
         }
     }
@@ -182,11 +189,16 @@ impl Node {
     }
 }
 
-struct PoolIter<'a> {
+/// An iterator over the trailing nodes.
+struct NodeIter<'a> {
+    /// The next node of this iterator.
+    ///
+    /// If there is another element, it will be returned on next iteration. If not, this field is
+    /// `None` and the iterator is over.
     node: Option<&'a mut Node>,
 }
 
-impl<'a> Iterator for PoolIter<'a> {
+impl<'a> Iterator for NodeIter<'a> {
     type Item = &'a mut Node;
 
     fn next(&mut self) -> &'a mut Node {

+ 31 - 0
src/bk/pool.rs

@@ -1,3 +1,5 @@
+//! The memory bookkeeping structure.
+
 use core::{cmp, mem};
 
 use arena::Arena;
@@ -5,12 +7,41 @@ use bk::search::{self, Search};
 use ptr;
 use random;
 
+/// The memory allocation pool.
+///
+/// This is the cetnral structure of `ralloc`. The block pool holds and organizes the memory such
+/// that it can be retreived and inserted efficiently.
+///
+/// The block pool organizes memory as nodes each holding some contiguous memory segment. Two
+/// neighboring blocks are never adjacent, since the segment is maximized and adjacent blocks are
+/// merged.
+///
+/// These nodes forms a skip list, which is a randomized data structure resembling a tree.
+/// Essentially, this means that every node has a stack of "shortcuts", which points to later
+/// nodes. The higher in this stack, the more nodes said shortcut skips, hence the name:
+///
+/// ![A diagram of a basic setup](https://i.imgur.com/Fd6gDLv.png)
+///
+/// Now, `ralloc` extends skip lists in a crucial way. Every shortcut contains the biggest
+/// contagious memory segment it skipped. This allows us to cheaply retrieve the biggest block,
+/// which can be broken down to a fitting size, i.e. memory allocation.
+///
+/// Because having the allocator managing its own memory gets hairy, we save up some memory in
+/// advance. This memory is stored in an arena structure, which links free pieces together.
 struct Pool {
+    /// The head (first node) of the block pool.
     head: Node,
+    /// The arena which provides the memory for the nodes.
     arena: Arena<Node>,
 }
 
 impl Pool {
+    /// Update the pool.
+    ///
+    /// This should be runned after state changes in order to maintain the capacity of the arena.
+    /// In particular, if the arena gets too low it cannot obtain more capacity without infinite
+    /// recursion, as a result of the allocation itself needing an arena. As such, there must be
+    /// some elements in excess. This function maintains that.
     pub fn update(&mut self) {
         /// The threshold to be reached before a refill happens.
         const REFILL_THRESHOLD: arena::Length = arena::Length(2);

+ 5 - 0
src/bk/search.rs

@@ -4,6 +4,11 @@ use bk::node::Node;
 use bk::shortcut::Shortcut;
 use block::{self, Block};
 
+/// Types defining a pool search target.
+///
+/// This trait is implemented for types which define some target or needle in the pool structure.
+///
+/// This exists in order to make searching generics and eliminate duplicate code.
 pub trait Search {
     /// Determine if this shortcut skips the needle.
     ///

+ 27 - 4
src/bk/seek.rs

@@ -1,6 +1,14 @@
+//! Structures for representing block pool search results.
+//!
+//! When the pool have is searched, the result needs to be represented in a way that allows us to
+//! actually use the result by modifying it. This module provides such features.
+
 /// A "seek".
 ///
 /// Seek represents the a found node ("needle") and backtracking information ("lookback").
+///
+/// Seeks makes it possible to retrospectively modify the jumped nodes based on the found node
+/// without performing the search again.
 struct Seek<'a> {
     /// The last node below our target for each level.
     ///
@@ -10,7 +18,7 @@ struct Seek<'a> {
     ///
     /// # An important note!
     ///
-    /// It is crucial that the backlook is pointers to nodes _before_ the target, not the target
+    /// It is crucial that the lookback is pointers to nodes _before_ the target, not the target
     /// node itself.
     ///
     /// Hence, the lookback cannot contain a pointer to `self.node` itself. This is an important
@@ -76,17 +84,21 @@ impl<'a> Seek<'a> {
         }
     }
 
+    /// Put a block next to the target.
+    ///
+    /// This will initially try to merge the given block with the found target. If this suceeds, it
+    /// will try to merge to the next block to maimize the block. If this fails, it will insert the
+    /// block by creating new nodes and links.
     fn put(&mut self, block: Block, arena: &mut Arena<Node>) {
         log!(INTERNAL, "Putting block {:?} into the block pool.", block);
 
         if self.node.block.merge_right(block).is_ok() {
-            log!(INTERNAL, "Merged the block to the left.", block);
-
             // Merge suceeded:
             //               [==block==]
             //     [==self==]            [==rest==]
             // is now:
             //     [==self=============] [==rest==]
+            log!(INTERNAL, "Merged the block to the left.", block);
 
             // Update the fat values.
             self.increase_fat(self.node.block.size(), Level::min());
@@ -124,6 +136,9 @@ impl<'a> Seek<'a> {
         }
     }
 
+    /// Try to merge the target node into the right-hand node.
+    ///
+    /// This will merge adjacent blocks, if possible.
     fn try_merge_right(&mut self, arena: &mut Arena<Node>) {
         if self.node.block.merge_right(self.node.next.block).is_ok() {
             log!(INTERNAL, "Merged node (block :?) right.", self.node.block);
@@ -366,7 +381,10 @@ impl<'a> Seek<'a> {
         }
     }
 
-    /// Check this seek.
+    /// Check this seek for broken invariants.
+    ///
+    /// This performs a runtime checks on the guarantees and invariants specified elsewhere in the
+    /// documentation.
     ///
     /// This is NOOP in release mode.
     fn check(&self) {
@@ -414,8 +432,13 @@ impl<'a> Seek<'a> {
     }
 }
 
+/// An iterator over the skips of the seek.
+///
+/// This iterates over the respective shortcut of each entry in the lookback.
 struct Skips<'a> {
+    /// The seek we're iterating over.
     seek: &'a Seek<'a>,
+    /// The current level.
     levels: lv::Iter,
 }
 

+ 16 - 0
src/bk/shortcut.rs

@@ -1,3 +1,7 @@
+//! Shortcuts in the skip list.
+//!
+//! Just bidning 'em nodes together.
+
 use block;
 
 /// A shortcut (a pointer to a later node and the size of the biggest block it skips).
@@ -39,6 +43,12 @@ struct Shortcut {
 }
 
 impl Shortcut {
+    /// Is this shortcut null?
+    ///
+    /// The fat value set to 0 is used as a trap value representing shortcuts _above_ the height of
+    /// the node, i.e. the entries not filled. For example, if a node has height 5 and the max
+    /// height is 8, then the tree last entries of the shortcut array are filled with null
+    /// shortcuts.
     #[inline]
     fn is_null(&self) -> bool {
         self.fat == 0
@@ -99,8 +109,14 @@ impl Shortcut {
     }
 }
 
+/// An iterator over shortcuts of some level.
+///
+/// This will skip go to the shortcutted ($$n$$'th shortcut) node and take the shortcut on level
+/// $$n$$. As such, it will go over shortcuts of level $$n$$, in order.
 struct ShortcutIter<'a> {
+    /// The level of the shortcuts we're iterating over.
     lv: Level,
+    /// The shortcut to be returned next iteration.
     shortcut: Option<&'a Shortcut>,
 }