mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-26 13:04:16 +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