// Mark the given block as free. The given block must have been // wholly allocated before, but it's legal to release big // allocations in smaller non-overlapping sub-blocks. public void Release(HeapBlock block) { // Merge the newly released block with any free blocks on either // side of it. Remove those blocks from the list of free blocks, // as they no longer exist as separate blocks. block = Coalesce(block); SizeBin bin = new SizeBin(block); int index = FindSmallestSufficientBin(bin); // If the exact bin doesn't exist, add it. if (index >= m_SizeBins.Length || bin.CompareTo(m_SizeBins[index]) != 0) { index = AddNewBin(ref bin, index); } m_Blocks[m_SizeBins[index].blocksId].Push(block); m_Free += block.Length; #if DEBUG_ASSERTS Assert.IsFalse(m_FreeEndpoints.ContainsKey(block.begin)); Assert.IsFalse(m_FreeEndpoints.ContainsKey(block.end)); #endif // Store both endpoints of the free block to the hashmap for // easy coalescing. m_FreeEndpoints[block.begin] = block.end; m_FreeEndpoints[block.end] = block.begin; }
private HeapBlock CutAllocationFromBlock(SizeBin allocation, HeapBlock block) { #if DEBUG_ASSERTS Assert.IsTrue(block.Length >= allocation.Size, "Block is not large enough."); #endif // If the match is exact, no need to cut. if (allocation.Size == block.Length) { return(block); } // Otherwise, round the begin to next multiple of alignment, and then cut away the required size, // potentially leaving empty space on both ends. ulong alignedBegin = NextAligned(block.begin, allocation.AlignmentLog2); ulong alignedEnd = alignedBegin + allocation.Size; if (alignedBegin > block.begin) { Release(new HeapBlock(block.begin, alignedBegin)); } if (alignedEnd < block.end) { Release(new HeapBlock(alignedEnd, block.end)); } return(new HeapBlock(alignedBegin, alignedEnd)); }
private unsafe int AddNewBin(ref SizeBin bin, int index) { // If there are no free block lists, make a new one if (m_BlocksFreelist.IsEmpty) { bin.blocksId = m_Blocks.Length; m_Blocks.Add(new BlocksOfSize(0)); } else { int last = m_BlocksFreelist.Length - 1; bin.blocksId = m_BlocksFreelist[last]; m_BlocksFreelist.ResizeUninitialized(last); } #if DEBUG_ASSERTS Assert.IsTrue(m_Blocks[bin.blocksId].Empty); #endif int tail = m_SizeBins.Length - index; m_SizeBins.ResizeUninitialized(m_SizeBins.Length + 1); SizeBin *p = (SizeBin *)m_SizeBins.GetUnsafePtr(); UnsafeUtility.MemMove( p + (index + 1), p + index, tail * UnsafeUtility.SizeOf <SizeBin>()); p[index] = bin; return(index); }
// Attempt to allocate a block from the heap with at least the given // size and alignment. The allocated block might be bigger than the // requested size, but will never be smaller. // If the allocation fails, an empty block is returned. public HeapBlock Allocate(ulong size, uint alignment = 1) { // Always use at least the minimum alignment, and round all sizes // to multiples of the minimum alignment. size = NextAligned(size, m_MinimumAlignmentLog2); alignment = math.max(alignment, MinimumAlignment); SizeBin allocBin = new SizeBin(size, alignment); int index = FindSmallestSufficientBin(allocBin); while (index < m_SizeBins.Length) { SizeBin bin = m_SizeBins[index]; if (CanFitAllocation(allocBin, bin)) { HeapBlock block = PopBlockFromBin(bin, index); return(CutAllocationFromBlock(allocBin, block)); } else { ++index; } } return(new HeapBlock()); }
private unsafe HeapBlock PopBlockFromBin(SizeBin bin, int index) { HeapBlock block = m_Blocks[bin.blocksId].Pop(); RemoveEndpoints(block); m_Free -= block.Length; RemoveBinIfEmpty(bin, index); return(block); }
private unsafe int AddNewBin(SizeBin bin, int index) { int tail = m_SizeBins.Length - index; m_SizeBins.ResizeUninitialized(m_SizeBins.Length + 1); SizeBin *p = (SizeBin *)m_SizeBins.GetUnsafePtr(); UnsafeUtility.MemMove( p + (index + 1), p + index, tail * UnsafeUtility.SizeOf <SizeBin>()); p[index] = bin; return(index); }
private unsafe void RemoveEmptyBins(SizeBin bin, int index) { if (!m_Blocks[bin.blocksId].Empty) { return; } int tail = m_SizeBins.Length - (index + 1); SizeBin *p = (SizeBin *)m_SizeBins.GetUnsafePtr(); UnsafeUtility.MemMove( p + index, p + (index + 1), tail * UnsafeUtility.SizeOf <SizeBin>()); m_SizeBins.ResizeUninitialized(m_SizeBins.Length - 1); }
private int FindSmallestSufficientBin(SizeBin needle) { if (m_SizeBins.Length == 0) { return(0); } int lo = 0; // Low endpoint of search, inclusive int hi = m_SizeBins.Length; // High endpoint of search, exclusive for (;;) { int d2 = (hi - lo) / 2; // Search has terminated. If lo is large enough, return it. if (d2 == 0) { if (needle.CompareTo(m_SizeBins[lo]) <= 0) { return(lo); } else { return(lo + 1); } } int probe = lo + d2; int cmp = needle.CompareTo(m_SizeBins[probe]); // Needle is smaller than probe? if (cmp < 0) { hi = probe; } // Needle is greater than probe? else if (cmp > 0) { lo = probe; } // Found needle exactly. else { return(probe); } } }
private void RemoveFreeBlock(HeapBlock block) { RemoveEndpoints(block); SizeBin bin = new SizeBin(block); int index = FindSmallestSufficientBin(bin); #if DEBUG_ASSERTS Assert.IsTrue(index >= 0 && m_SizeBins[index].sizeClass == bin.sizeClass, "Expected to find exact match for size bin since block was supposed to exist"); #endif bool removed = m_Blocks[m_SizeBins[index].blocksId].Remove(block); RemoveBinIfEmpty(m_SizeBins[index], index); #if DEBUG_ASSERTS Assert.IsTrue(removed, "Block was supposed to exist"); #endif m_Free -= block.Length; }
private bool CanFitAllocation(SizeBin allocation, SizeBin bin) { #if DEBUG_ASSERTS Assert.IsTrue(bin.sizeClass >= allocation.sizeClass, "Should have compatible size classes to begin with"); #endif // Check that the bin is not empty. if (m_Blocks[bin.blocksId].Empty) { return(false); } // If the bin meets alignment restrictions, it is usable. if (bin.HasCompatibleAlignment(allocation)) { return(true); } // Else, require one alignment worth of extra space so we can guarantee space. else { return(bin.Size >= (allocation.Size + allocation.Alignment)); } }
// Do a slow exhaustive test of the allocator's internal state to verify // that its invariants have not been broken. Should be only used for debugging. public void DebugValidateInternalState() { // Amount of size bins should be the same as the amount of block lists // for those size bins. int numBins = m_SizeBins.Length; int numFreeBlockLists = m_BlocksFreelist.Length; int numEmptyBlocks = 0; int numNonEmptyBlocks = 0; for (int i = 0; i < m_Blocks.Length; ++i) { if (m_Blocks[i].Empty) { ++numEmptyBlocks; } else { ++numNonEmptyBlocks; } } Assert.AreEqual(numBins, numNonEmptyBlocks, "There should be exactly one non-empty block list per size bin"); Assert.AreEqual(numEmptyBlocks, numFreeBlockLists, "All empty block lists should be in the free list"); for (int i = 0; i < m_BlocksFreelist.Length; ++i) { int freeBlock = m_BlocksFreelist[i]; Assert.IsTrue(m_Blocks[freeBlock].Empty, "There should be only empty block lists in the free list"); } ulong totalFreeSize = 0; int totalFreeBlocks = 0; for (int i = 0; i < m_SizeBins.Length; ++i) { var sizeBin = m_SizeBins[i]; var size = sizeBin.Size; var align = sizeBin.Alignment; var blocks = m_Blocks[sizeBin.blocksId]; Assert.IsFalse(blocks.Empty, "All block lists should be non-empty, empty lists should be removed"); int count = blocks.Length; for (int j = 0; j < count; ++j) { var b = blocks.Block(j); var bin = new SizeBin(b); Assert.AreEqual(size, bin.Size, "Block size should match its bin"); Assert.AreEqual(align, bin.Alignment, "Block alignment should match its bin"); totalFreeSize += b.Length; if (m_FreeEndpoints.TryGetValue(b.begin, out var foundEnd)) { Assert.AreEqual(b.end, foundEnd, "Free block end does not match stored endpoint"); } else { Assert.IsTrue(false, "No end endpoint found for free block"); } if (m_FreeEndpoints.TryGetValue(b.end, out var foundBegin)) { Assert.AreEqual(b.begin, foundBegin, "Free block begin does not match stored endpoint"); } else { Assert.IsTrue(false, "No begin endpoint found for free block"); } ++totalFreeBlocks; } } // Reported free space should be equal to the total size of the free blocks Assert.AreEqual(totalFreeSize, FreeSpace, "Free size reported incorrectly"); Assert.IsTrue(totalFreeSize <= Size, "Amount of free size larger than maximum"); Assert.AreEqual(2 * totalFreeBlocks, m_FreeEndpoints.Count(), "Each free block should have exactly 2 stored endpoints"); }