mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 22:56:30 -10:00
202 lines
6.3 KiB
C++
202 lines
6.3 KiB
C++
#include "utils/HeapAllocator.h"
|
|
|
|
namespace {
|
|
|
|
const size_t HEAP_SIZE_INCREASE = 1 * 1024 * 1024; // Always increment in 1MB steps
|
|
const size_t HEAP_MAX_INCREASE = 20 * 1024 * 1024; // never increase heap size by more than 20MB
|
|
|
|
template<typename TIter, typename T>
|
|
TIter get_element_before(TIter first, TIter last, const T& value) {
|
|
if (first == last) {
|
|
// There are no elements in the range
|
|
return last;
|
|
}
|
|
|
|
auto iter = std::upper_bound(first, last, value);
|
|
|
|
if (iter == first) {
|
|
// The element before this does not exist
|
|
return last;
|
|
}
|
|
|
|
// We found the position (which may be last) so now we can decrement it once to get the element before this found position
|
|
return std::next(iter, -1);
|
|
}
|
|
|
|
}
|
|
|
|
namespace util {
|
|
HeapAllocator::HeapAllocator(const HeapAllocator::HeapResizer& creator) : _heapResizer(creator) {
|
|
_heapSize = HEAP_SIZE_INCREASE;
|
|
_lastSizeIncraese = HEAP_SIZE_INCREASE;
|
|
_heapResizer(_heapSize);
|
|
|
|
MemoryRange range;
|
|
range.offset = 0;
|
|
range.size = _heapSize;
|
|
|
|
addFreeRange(range);
|
|
}
|
|
size_t HeapAllocator::allocate(size_t size) {
|
|
// Do a simple first-fit algorithm. Not very efficient but it should do the job well for our purposes
|
|
for (auto iter = _freeRanges.begin(); iter != _freeRanges.end(); ++iter) {
|
|
if (iter->size >= size) {
|
|
// Found a match!
|
|
auto offset = iter->offset;
|
|
|
|
iter->offset += size;
|
|
iter->size -= size;
|
|
|
|
if (iter->size == 0) {
|
|
// This range is now empty and must be removed. Since we won't be using this iterator anymore it can be
|
|
// safely erased
|
|
_freeRanges.erase(iter);
|
|
}
|
|
|
|
MemoryRange allocated;
|
|
allocated.size = size;
|
|
allocated.offset = offset;
|
|
|
|
addAllocatedRange(allocated);
|
|
|
|
return offset;
|
|
}
|
|
}
|
|
|
|
// No free range found => increase size of heap
|
|
|
|
// We increase the heap size every time we run out of memory in order to reduce reallocation times
|
|
_lastSizeIncraese = std::min(HEAP_MAX_INCREASE, 2 * _lastSizeIncraese);
|
|
|
|
// Make sure that our allocation can actually fit into the new block if its too large for a single increase
|
|
auto increase = std::max(HEAP_SIZE_INCREASE, _lastSizeIncraese);
|
|
auto lastOffset = _heapSize;
|
|
|
|
_heapSize += increase;
|
|
_heapResizer(_heapSize);
|
|
|
|
MemoryRange newFree;
|
|
newFree.offset = lastOffset;
|
|
newFree.size = increase;
|
|
|
|
addFreeRange(newFree);
|
|
|
|
// Call us again but this time it should succeed
|
|
return allocate(size);
|
|
}
|
|
void HeapAllocator::addFreeRange(const HeapAllocator::MemoryRange& range) {
|
|
if (range.size == 0) {
|
|
// Ignore empty ranges
|
|
return;
|
|
}
|
|
|
|
Assertion(std::is_sorted(_freeRanges.begin(), _freeRanges.end()),
|
|
"Free ranges were not sorted before adding a free range.");
|
|
|
|
auto left = get_element_before(_freeRanges.begin(), _freeRanges.end(), range);
|
|
auto right = std::upper_bound(_freeRanges.begin(), _freeRanges.end(), range);
|
|
|
|
if (left != _freeRanges.end()) {
|
|
// This new range may be just after another
|
|
if (left->offset + left->size == range.offset) {
|
|
left->size += range.size;
|
|
|
|
// After this operation the two ranges may be connected. If that happens then they need to be merged as well
|
|
if (right != _freeRanges.end() && check_connected_range(*left, *right)) {
|
|
left->size += right->size;
|
|
_freeRanges.erase(right);
|
|
}
|
|
|
|
checkRangesMerged();
|
|
|
|
// The range has been added by merging it with another
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (right != _freeRanges.end()) {
|
|
// This new range may be just before another
|
|
if (range.offset + range.size == right->offset) {
|
|
right->offset = range.offset;
|
|
right->size += range.size;
|
|
|
|
// After this operation the two ranges may be connected. If that happens then they need to be merged as well
|
|
if (left != _freeRanges.end() && check_connected_range(*left, *right)) {
|
|
left->size += right->size;
|
|
_freeRanges.erase(right);
|
|
}
|
|
|
|
checkRangesMerged();
|
|
|
|
// The range has been added by merging it with another
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If we are here then we found no range to merge the new range into
|
|
_freeRanges.insert(right, range);
|
|
|
|
checkRangesMerged();
|
|
}
|
|
void HeapAllocator::addAllocatedRange(const HeapAllocator::MemoryRange& range) {
|
|
Assertion(std::is_sorted(_allocatedRanges.begin(), _allocatedRanges.end()), "Allocated ranges are not sorted!");
|
|
Assertion(!std::binary_search(_allocatedRanges.begin(), _allocatedRanges.end(), range),
|
|
"Allocated ranges already contain the specified range!");
|
|
|
|
// We need to keep the vector sorted for better search performance so we insert this new range at the sorted position
|
|
// into the vector
|
|
auto it = std::upper_bound(_allocatedRanges.begin(), _allocatedRanges.end(), range);
|
|
|
|
// This works even if it is .end() since .insert will just add it to the vector in that case
|
|
_allocatedRanges.insert(it, range);
|
|
|
|
Assertion(std::is_sorted(_allocatedRanges.begin(), _allocatedRanges.end()), "Allocated ranges are not sorted!");
|
|
}
|
|
void HeapAllocator::free(size_t offset) {
|
|
// We use a dummy range for finding the entry in our allocated ranges
|
|
MemoryRange range;
|
|
range.offset = offset;
|
|
|
|
auto it = std::lower_bound(_allocatedRanges.begin(), _allocatedRanges.end(), range);
|
|
|
|
// Make sure that the range is valid
|
|
Assertion(it != _allocatedRanges.end(), "Specified offset was not found in the allocated ranges!");
|
|
Assertion(it->offset == offset, "Specified offset does not point to the start of an allocated range!");
|
|
|
|
// Copy the range out of the vector before erasing it so we can add it to the free ranges
|
|
auto freedRange = *it;
|
|
|
|
_allocatedRanges.erase(it);
|
|
|
|
addFreeRange(freedRange);
|
|
}
|
|
size_t HeapAllocator::numAllocations() const {
|
|
return _allocatedRanges.size();
|
|
}
|
|
bool HeapAllocator::check_connected_range(const MemoryRange& left, const MemoryRange& right) {
|
|
return left.offset + left.size == right.offset;
|
|
}
|
|
void HeapAllocator::checkRangesMerged() {
|
|
// This is a debug only function because its values are only used in debug and linters will get tripped up otherwise.
|
|
#ifndef NDEBUG
|
|
bool first = true;
|
|
size_t lastEnd = 0;
|
|
for (auto& range : _freeRanges) {
|
|
if (!first) {
|
|
Assertion(lastEnd != range.offset, "Found unmerged ranges at offset " SIZE_T_ARG "!", lastEnd);
|
|
}
|
|
|
|
lastEnd = range.offset + range.size;
|
|
first = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool HeapAllocator::MemoryRange::operator<(const HeapAllocator::MemoryRange& other) const {
|
|
return offset < other.offset;
|
|
}
|
|
bool HeapAllocator::MemoryRange::operator==(const HeapAllocator::MemoryRange& other) const {
|
|
return offset == other.offset;
|
|
}
|
|
}
|