#pragma once
#include <memory>
#include <optional>
#include <glog/logging.h>
#include "mutex.h"
namespace mooncake::offset_allocator {
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint32;
using NodeIndex = uint32;
class OffsetAllocator;
class __Allocator;
static constexpr uint32 NUM_TOP_BINS = 32;
static constexpr uint32 BINS_PER_LEAF = 8;
static constexpr uint32 TOP_BINS_INDEX_SHIFT = 3;
static constexpr uint32 LEAF_BINS_INDEX_MASK = 0x7;
static constexpr uint32 NUM_LEAF_BINS = NUM_TOP_BINS * BINS_PER_LEAF;
struct OffsetAllocation {
static constexpr uint32 NO_SPACE = 0xffffffff;
private:
uint32 offset = NO_SPACE;
NodeIndex metadata = NO_SPACE;
public:
OffsetAllocation(uint32 offset_param, NodeIndex metadata_param)
: offset(offset_param), metadata(metadata_param) {}
uint64_t getOffset() const { return static_cast<uint64_t>(offset); }
bool isNoSpace() const { return offset == NO_SPACE; }
friend class __Allocator;
};
struct OffsetAllocStorageReport {
uint64_t totalFreeSpace;
uint64_t largestFreeRegion;
};
struct OffsetAllocStorageReportFull {
struct Region {
uint64_t size;
uint64_t count;
};
Region freeRegions[NUM_LEAF_BINS];
};
class OffsetAllocationHandle {
public:
OffsetAllocationHandle(std::shared_ptr<OffsetAllocator> allocator,
OffsetAllocation allocation, uint64_t base,
uint64_t size);
OffsetAllocationHandle(OffsetAllocationHandle&& other) noexcept;
OffsetAllocationHandle& operator=(OffsetAllocationHandle&& other) noexcept;
OffsetAllocationHandle(const OffsetAllocationHandle&) = delete;
OffsetAllocationHandle& operator=(const OffsetAllocationHandle&) = delete;
~OffsetAllocationHandle();
bool isValid() const { return !m_allocator.expired(); }
uint64_t address() const { return real_base; }
void* ptr() const { return reinterpret_cast<void*>(address()); }
uint64_t size() const { return requested_size; }
private:
std::weak_ptr<OffsetAllocator> m_allocator;
OffsetAllocation m_allocation;
uint64_t real_base;
uint64_t requested_size;
friend class OffsetAllocatorTest;
};
struct OffsetAllocatorMetrics {
uint64_t allocated_size_;
uint64_t allocated_num_;
uint64_t largest_free_region_;
uint64_t total_free_space_;
const uint64_t capacity;
};
std::ostream& operator<<(std::ostream& os,
const OffsetAllocatorMetrics& metrics);
class OffsetAllocator : public std::enable_shared_from_this<OffsetAllocator> {
public:
static std::shared_ptr<OffsetAllocator> create(
uint64_t base, size_t size, uint32 init_capacity = 128 * 1024,
uint32 max_capacity = (1 << 20));
OffsetAllocator(const OffsetAllocator&) = delete;
OffsetAllocator& operator=(const OffsetAllocator&) = delete;
OffsetAllocator(OffsetAllocator&& other) noexcept = delete;
OffsetAllocator& operator=(OffsetAllocator&& other) noexcept = delete;
~OffsetAllocator() = default;
[[nodiscard]]
std::optional<OffsetAllocationHandle> allocate(size_t size);
[[nodiscard]]
OffsetAllocStorageReport storageReport() const;
[[nodiscard]]
OffsetAllocStorageReportFull storageReportFull() const;
[[nodiscard]]
OffsetAllocatorMetrics get_metrics() const;
template <typename T>
void serialize_to(T& serializer) const;
template <typename T>
static std::shared_ptr<OffsetAllocator> deserialize_from(T& serializer);
private:
friend class OffsetAllocationHandle;
void freeAllocation(const OffsetAllocation& allocation, uint64_t size);
[[nodiscard]]
OffsetAllocatorMetrics get_metrics_internal() const REQUIRES(m_mutex);
std::unique_ptr<__Allocator> m_allocator GUARDED_BY(m_mutex);
uint64_t m_base;
uint64_t m_multiplier_bits;
uint64_t m_capacity;
mutable Mutex m_mutex;
uint64_t m_allocated_size GUARDED_BY(m_mutex) = 0;
uint64_t m_allocated_num GUARDED_BY(m_mutex) = 0;
OffsetAllocator(uint64_t base, size_t size, uint32 init_capacity,
uint32 max_capacity);
template <typename T>
OffsetAllocator(T& serializer);
friend class OffsetAllocatorTest;
};
class __Allocator {
public:
__Allocator(uint32 size, uint32 init_capacity, uint32 max_capacity);
template <typename T>
__Allocator(T& serializer) noexcept(false);
__Allocator(__Allocator&& other);
~__Allocator();
void reset();
OffsetAllocation allocate(uint32 size);
void free(OffsetAllocation allocation);
uint32 allocationSize(OffsetAllocation allocation) const;
OffsetAllocStorageReport storageReport() const;
OffsetAllocStorageReportFull storageReportFull() const;
template <typename T>
void serialize_to(T& serializer) const;
private:
uint32 insertNodeIntoBin(uint32 size, uint32 dataOffset);
void removeNodeFromBin(uint32 nodeIndex);
struct Node {
static constexpr NodeIndex unused = 0xffffffff;
uint32 dataOffset = 0;
uint32 dataSize = 0;
NodeIndex binListPrev = unused;
NodeIndex binListNext = unused;
NodeIndex neighborPrev = unused;
NodeIndex neighborNext = unused;
bool used = false;
};
uint32 m_size;
uint32 m_current_capacity;
uint32 m_max_capacity;
uint32 m_freeStorage;
uint32 m_usedBinsTop;
uint8 m_usedBins[NUM_TOP_BINS];
NodeIndex m_binIndices[NUM_LEAF_BINS];
Node* m_nodes;
NodeIndex* m_freeNodes;
uint32 m_freeOffset;
friend class OffsetAllocatorTest;
};
template <typename T>
void OffsetAllocator::serialize_to(T& serializer) const {
MutexLocker guard(&m_mutex);
if (!m_allocator) {
serializer.set_error("Allocator is not initialized");
return;
}
serializer.write(&m_base, sizeof(m_base));
serializer.write(&m_multiplier_bits, sizeof(m_multiplier_bits));
serializer.write(&m_capacity, sizeof(m_capacity));
serializer.write(&m_allocated_size, sizeof(m_allocated_size));
serializer.write(&m_allocated_num, sizeof(m_allocated_num));
m_allocator->serialize_to(serializer);
}
template <typename T>
std::shared_ptr<OffsetAllocator> OffsetAllocator::deserialize_from(
T& serializer) {
return std::shared_ptr<OffsetAllocator>(new OffsetAllocator(serializer));
}
template <typename T>
OffsetAllocator::OffsetAllocator(T& serializer) {
try {
serializer.read(&m_base, sizeof(m_base));
serializer.read(&m_multiplier_bits, sizeof(m_multiplier_bits));
serializer.read(&m_capacity, sizeof(m_capacity));
serializer.read(&m_allocated_size, sizeof(m_allocated_size));
serializer.read(&m_allocated_num, sizeof(m_allocated_num));
m_allocator = std::make_unique<__Allocator>(serializer);
} catch (const std::exception& e) {
LOG(ERROR) << "Deserializing OffsetAllocator failed, error="
<< e.what();
throw std::runtime_error("Deserializing OffsetAllocator failed");
}
}
template <typename T>
void __Allocator::serialize_to(T& serializer) const {
if (!m_nodes || !m_freeNodes) {
serializer.set_error("Allocator is not initialized");
return;
}
serializer.write(&m_size, sizeof(m_size));
serializer.write(&m_current_capacity, sizeof(m_current_capacity));
serializer.write(&m_max_capacity, sizeof(m_max_capacity));
serializer.write(&m_freeStorage, sizeof(m_freeStorage));
serializer.write(&m_usedBinsTop, sizeof(m_usedBinsTop));
serializer.write(&m_usedBins, sizeof(m_usedBins));
serializer.write(&m_binIndices, sizeof(m_binIndices));
serializer.write(&m_freeOffset, sizeof(m_freeOffset));
serializer.write(m_nodes, m_current_capacity * sizeof(Node));
serializer.write(m_freeNodes, m_current_capacity * sizeof(NodeIndex));
}
template <typename T>
__Allocator::__Allocator(T& serializer) {
m_nodes = nullptr;
m_freeNodes = nullptr;
try {
serializer.read(&m_size, sizeof(m_size));
serializer.read(&m_current_capacity, sizeof(m_current_capacity));
serializer.read(&m_max_capacity, sizeof(m_max_capacity));
serializer.read(&m_freeStorage, sizeof(m_freeStorage));
serializer.read(&m_usedBinsTop, sizeof(m_usedBinsTop));
serializer.read(&m_usedBins, sizeof(m_usedBins));
serializer.read(&m_binIndices, sizeof(m_binIndices));
serializer.read(&m_freeOffset, sizeof(m_freeOffset));
m_nodes = new Node[m_max_capacity];
m_freeNodes = new NodeIndex[m_max_capacity];
serializer.read(m_nodes, m_current_capacity * sizeof(Node));
serializer.read(m_freeNodes, m_current_capacity * sizeof(NodeIndex));
} catch (const std::exception& e) {
LOG(ERROR) << "Deserializing __Allocator failed, error=" << e.what();
if (m_nodes) delete[] m_nodes;
if (m_freeNodes) delete[] m_freeNodes;
throw std::runtime_error("Deserializing __Allocator failed");
}
}
}