Skip to content

Commit

Permalink
Add new HeapSmall.PruneSMT method with tests and documentation
Browse files Browse the repository at this point in the history
This allows the user to prune useless pages from the SMT, if it has grown too much
  • Loading branch information
quajak committed Oct 22, 2023
1 parent b0d5a9b commit d26f463
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 2 deletions.
15 changes: 14 additions & 1 deletion Docs/articles/Kernel/MemoryManagement.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,16 @@ The RAT is managed through the `RAT` class. Pages are allocated via `void* RAT.A

The Heap itself is managed by the `Heap` class. It contains the mechanism to allocate (`byte* Heap.Alloc(uint aSize)`), re-allocate ('byte* Heap.Realloc(byte* aPtr, uint newSize)') and free (`void Heap.Free(void* aPtr)`) objects of various sizes. Objects are seperated by size in bytes into Small (Smaller than 1/4 Page), Medium (Smaller than 1 Page) and Large (Larger than 1 Page). Currently Medium and Large objects are managed the same way using the methods in `HeapLarge` which do little more than allocating/freeing the necessary number of pages. Small objects are managed differently in `HeapSmall`.

Small Objects are managed using the SMT (Size Map Table), which is initalised using `void HeapSmall.InitSMT(uint aMaxItemSize)`. The basic idea of the SMT is to allocate objects of similar sizes on the same page. The SMT grows dynamically as required. The SMT is made up of a series of pages, each of which contains a series of `RootSMTBlock` each of which link to a chain of `SMTBlock`. The `RootSMTBlock` can be thought of as column headers and the `SMTBlock` as the elements stored in the column. The `RootSMTBlock` are a linked list, each containing the maximum object size stored in its pages, the location of the first `SMTBlock` for this size, and the location of the next `RootSMTBlock`. The list is in ascending order of size, so that the smallest large enough `RootSMTBlock` is found first. A `SMTBlock` contains a pointer to the actual page where objects are stored, how much space is left on that page, and a pointer to the next `SMTBlock`. If every `SMTBlock` for a certain size is full, a new `SMTBlock` is allocated. The page linked to by the `SMTBlock` is split into an array of spaces, each large enough to allocate an object of maximum size with header, which can be iterated through via index and fixed size when allocating. Each object allocated on the `HeapSmall` has a header of 2 `ushort`, the first one storing the actual size of the object and the second, the GC status of the object.
Small Objects are managed using the SMT (Size Map Table), which is initalised using `void HeapSmall.InitSMT(uint aMaxItemSize)`.
The basic idea of the SMT is to allocate objects of similar sizes on the same page. The SMT grows dynamically as required.
The SMT is made up of a series of pages, each of which contains a series of `RootSMTBlock` each of which link to a chain of `SMTBlock`.
The `RootSMTBlock` can be thought of as column headers and the `SMTBlock` as the elements stored in the column.
The `RootSMTBlock` are a linked list, each containing the maximum object size stored in its pages, the location of the first `SMTBlock` for this size, and the location of the next `RootSMTBlock`.
The list is in ascending order of size, so that the smallest large enough `RootSMTBlock` is found first.
A `SMTBlock` contains a pointer to the actual page where objects are stored, how much space is left on that page, and a pointer to the next `SMTBlock`.
If every `SMTBlock` for a certain size is full, a new `SMTBlock` is allocated.
The page linked to by the `SMTBlock` is split into an array of spaces, each large enough to allocate an object of maximum size with header, which can be iterated through via index and fixed size when allocating.
Each object allocated on the `HeapSmall` has a header of 2 `ushort`, the first one storing the actual size of the object and the second, the GC status of the object.

## Garbage Collection

Expand All @@ -45,6 +54,10 @@ The garbage collector has to be manually triggerd using the call `int Heap.Colle

Note that the GC does not track objects only pointed to by pointers. To ensure that the GC nevertheless does not incorrectly free objects, you can use `void GCImplementation.IncRootCount(ushort* aPtr)` to manually increase the references of your object by 1. Once you no longer need the object you can use `void GCImplementation.DecRootCount(ushort* aPtr)` to remove the manual reference, which allows the next `Heap.Collect` call to free the object.

`Heap.Collect` only cleans up the objects which are no longer used but will leave behind empty pages in the SMT.
These pages can be cleaned up using `HeapSmall.PruneSMT` which will return the number of pages it freed.
Note that if in future elements are reallocated, this will cause new pages in the SMT to be allocated again, so using this too often may not be useful.

## Automatically Trigger Garbage Collection

When `RAT.MinFreePages` is set to a positive value and the number of free pages (as tracked by `RAT.FreePageCount`) drops below this value, on page allocation `Heap.Collect` will automatically be called. Each time this happens the value in `RAT.GCTriggered` is incremented by one.
Expand Down
12 changes: 12 additions & 0 deletions Tests/Kernels/Cosmos.Compiler.Tests.TypeSystem/Kernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,18 @@ private unsafe void TestGarbageCollector()
StaticTestClass.B.FieldA = 10;
collected = Heap.Collect();
Assert.AreEqual(0, collected, "Storing elements in static class keeps them referenced");

for (int i = 0; i < 10_000; i++)
{
_ = new object();
}
Heap.Collect();
uint heapSmallPages = RAT.GetPageCount((byte)RAT.PageType.HeapSmall);
int freed = HeapSmall.PruneSMT();
uint afterPrune = RAT.GetPageCount((byte)RAT.PageType.HeapSmall);
Assert.IsTrue(heapSmallPages >= afterPrune, "Running PruneSMT does not increase the number of pages in use");
Assert.AreEqual(freed, heapSmallPages - afterPrune, "PruneSMT returns the correct number of pages freed");

}

#region Test Methods
Expand Down
76 changes: 76 additions & 0 deletions source/Cosmos.Core/Memory/HeapSmall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -546,5 +546,81 @@ private static int GetAllocatedObjectCount(SMTPage* aPage, uint aSize)
}

#endregion

#region Cleanup

/// <summary>
/// This function will free all pages allocated for small objects which are emnpty
/// </summary>
/// <returns>Number of pages freed</returns>
public static int PruneSMT()
{
int freed = 0;
SMTPage* page = SMT;
while (page != null)
{
freed += PruneSMT(page);
page = page->Next;
}
return freed;
}

/// <summary>
/// Prune all empty pages allocated on a certain page
/// </summary>
/// <param name="aPage"></param>
/// <returns></returns>
private static int PruneSMT(SMTPage* aPage)
{
int freed = 0;
RootSMTBlock* ptr = (RootSMTBlock*)aPage->First; // since both RootSMTBlock and SMTBlock have the same size (20) it doesnt matter if cast is wrong
while(ptr != null)
{
freed += PruneSMT(ptr, ptr->Size);
ptr = ptr->LargerSize;
}
return freed;
}

/// <summary>
/// Prune all empty pages which are linked to root block for a certain size
/// The root block or first one following it will not be removed!
/// </summary>
/// <param name="aBlock"></param>
/// <param name="aSize"></param>
/// <returns></returns>
private static int PruneSMT(RootSMTBlock* aBlock, uint aSize)
{
int freed = 0;
int maxElements = (int)(RAT.PageSize / (aSize + PrefixItemBytes));
SMTBlock* prev = aBlock->First;
SMTBlock* block = prev->NextBlock;
while(block != null)
{
if (block->SpacesLeft == maxElements)
{
// This block is currently empty so free it
prev->NextBlock = block->NextBlock;
RAT.Free(block->PagePtr);

uint* toCleanUp = (uint*) block;
block = prev->NextBlock;

toCleanUp[0] = 0;
toCleanUp[1] = 0;
toCleanUp[2] = 0;

freed++;
}
else
{
prev = block;
block = block->NextBlock;
}
}
return freed;
}

#endregion
}
}
16 changes: 15 additions & 1 deletion source/Cosmos.Core/Memory/RAT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@ public static uint GetFirstRATIndex(void* aPtr)
throw new Exception("Page type not found. Likely RAT is rotten.");
}

/// <summary>
/// Get the pointer pointing to the start of the position to which the current pointer is pointing
/// </summary>
/// <param name="aPtr"></param>
/// <returns></returns>
public static byte* GetPagePtr(void* aPtr)
{
return (byte*)aPtr - ((byte*)aPtr - RamStart) % PageSize;
Expand Down Expand Up @@ -326,7 +331,7 @@ public static void Free(uint aPageIdx)
byte* p = mRAT + aPageIdx;
*p = (byte)PageType.Empty;
FreePageCount++;
for (; p < mRAT + TotalPageCount; )
for (; p < mRAT + TotalPageCount;)
{
if (*++p != (byte)PageType.Extension)
{
Expand All @@ -336,5 +341,14 @@ public static void Free(uint aPageIdx)
FreePageCount++;
}
}

/// <summary>
/// Free the page this pointer points to
/// </summary>
/// <param name="aPtr"></param>
public static void Free(void* aPtr)
{
Free(GetFirstRATIndex(aPtr));
}
}
}

0 comments on commit d26f463

Please sign in to comment.