mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-30 03:04:17 +01:00
Add a single-header linear allocator implementation
This conforms to the C++ 'Allocator' named requirement allowing it to be used with any STL type and allows drastically reducing allocation times in cases which are suited for linear allocation.
This commit is contained in:
parent
e816256220
commit
a0275418d6
126
app/src/main/cpp/skyline/common/linear_allocator.h
Normal file
126
app/src/main/cpp/skyline/common/linear_allocator.h
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <common.h>
|
||||||
|
|
||||||
|
namespace skyline {
|
||||||
|
/**
|
||||||
|
* @brief Typeless allocation state holder for LinearAllocator<T>
|
||||||
|
* @tparam NewChunkSize Step size in bytes for overflow chunk allocations
|
||||||
|
*/
|
||||||
|
template<size_t NewChunkSize = (1024 * 1024)> // Default to 1MB
|
||||||
|
class LinearAllocatorState {
|
||||||
|
private:
|
||||||
|
std::vector<u8> mainChunk; //!< The primary backing for the allocator, will grow during `Reset` calls if the previous set of allocations overflowed
|
||||||
|
std::list<std::vector<u8>> overflowChunks; //!< Overflow backing chunks used to hold allocations that can't fit in `mainChunk` until it can be resized
|
||||||
|
u8 *ptr{}; //!< Points to a free region of memory of size `chunkRemainingBytes`
|
||||||
|
size_t chunkRemainingBytes{NewChunkSize}; //!< Remaining bytes left in the current chunk
|
||||||
|
|
||||||
|
size_t allocCount{}; //!< The number of currently unfreed allocations
|
||||||
|
|
||||||
|
public:
|
||||||
|
LinearAllocatorState() {
|
||||||
|
mainChunk.reserve(NewChunkSize);
|
||||||
|
ptr = mainChunk.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocates a contiguous chunk of memory of size `size` aligned to the maximum possible required alignment
|
||||||
|
*/
|
||||||
|
u8 *Allocate(size_t unalignedSize) {
|
||||||
|
// Align the size to the maximum required alignment for standard types
|
||||||
|
size_t size{util::AlignUp(unalignedSize, alignof(std::max_align_t))};
|
||||||
|
|
||||||
|
// Allocated memory cannot span across multiple chunks
|
||||||
|
if (size > NewChunkSize)
|
||||||
|
throw std::bad_alloc();
|
||||||
|
|
||||||
|
if (chunkRemainingBytes < size) {
|
||||||
|
// If there is no space left in the current chunk allocate a new overflow one
|
||||||
|
auto &newChunk{overflowChunks.emplace_back()};
|
||||||
|
newChunk.reserve(NewChunkSize);
|
||||||
|
ptr = newChunk.data();
|
||||||
|
chunkRemainingBytes = NewChunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto allocatedPtr{ptr};
|
||||||
|
|
||||||
|
// Move ourselves along
|
||||||
|
chunkRemainingBytes -= size;
|
||||||
|
ptr += size;
|
||||||
|
|
||||||
|
allocCount++;
|
||||||
|
|
||||||
|
return allocatedPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Decreases allocation count, should be called an equal number of times to `Allocate`
|
||||||
|
*/
|
||||||
|
void Deallocate() {
|
||||||
|
allocCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resizes the main chunk to fit any previously needed overflow chunks and allows memory to be reused again for further allocations
|
||||||
|
* @note There **must** be no allocations leftover when this is called
|
||||||
|
*/
|
||||||
|
void Reset() {
|
||||||
|
if (allocCount)
|
||||||
|
// If we still have allocations remaining then throw
|
||||||
|
throw std::bad_alloc();
|
||||||
|
|
||||||
|
if (size_t overflowSize{overflowChunks.size() * NewChunkSize}) {
|
||||||
|
// Expand the main chunk so that it can fit any previously needed overflow chunks
|
||||||
|
overflowChunks.clear();
|
||||||
|
mainChunk.reserve(overflowSize + mainChunk.capacity());
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = mainChunk.data();
|
||||||
|
chunkRemainingBytes = mainChunk.capacity();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Linear allocator conforming to the C++ 'Allocator' named requirement, forwards all allocation requests to the passed in state
|
||||||
|
*/
|
||||||
|
template<typename T, typename State = LinearAllocatorState<>>
|
||||||
|
class LinearAllocator {
|
||||||
|
private:
|
||||||
|
State &state; //!< Backing allocation state holder
|
||||||
|
|
||||||
|
public:
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
template<typename, typename> friend class LinearAllocator; //!< Required for copy ctor from other types
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note This is explicitly not explicit to avoid the need to repeat the allocator type when constructing a new object
|
||||||
|
*/
|
||||||
|
LinearAllocator(State &state) : state(state) {}
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
LinearAllocator(const LinearAllocator<U> &other) : state(other.state) {};
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
LinearAllocator(LinearAllocator<U> &&other) : state(other.state) {};
|
||||||
|
|
||||||
|
[[nodiscard]] T *allocate(size_t n) {
|
||||||
|
return reinterpret_cast<T *>(state.Allocate(sizeof(T) * n));
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(T *obj, size_t n) noexcept {
|
||||||
|
state.Deallocate();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const LinearAllocator &other) const noexcept {
|
||||||
|
return &state == &other.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const LinearAllocator &other) const noexcept {
|
||||||
|
return &state != &other.state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user