device/arena.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /// @file arena.cpp | ||
| 3 | /// @brief Sub-allocating arenas with VMA's virtual blocks. | ||
| 4 | #include "ernie/device/arena.h" | ||
| 5 | |||
| 6 | #include <algorithm> | ||
| 7 | #include <optional> | ||
| 8 | |||
| 9 | namespace ernie { | ||
| 10 | |||
| 11 | 103 | Arenas::Arena::~Arena() { | |
| 12 | 103 | if (block != VK_NULL_HANDLE) { | |
| 13 | 103 | vmaClearVirtualBlock(block); | |
| 14 | 103 | vmaDestroyVirtualBlock(block); | |
| 15 | } | ||
| 16 | 103 | } | |
| 17 | |||
| 18 | 109 | Arenas::Arenas(DeviceContext& context, MemoryUse use, std::string_view name, | |
| 19 | 109 | std::uint64_t arenaSize) | |
| 20 | 109 | : context_(context), use_(use), name_(name), | |
| 21 | 109 | arenaSize_(std::min(arenaSize, context.MaxBufferSize())) {} | |
| 22 | |||
| 23 | 109 | Arenas::~Arenas() { | |
| 24 | 218 | Quietly([this] { context_.WaitIdle(); }); | |
| 25 | 109 | } | |
| 26 | |||
| 27 | 782 | BufferRange Arenas::Allocate(std::uint64_t size, std::uint64_t alignment) { | |
| 28 | 782 | if (size > context_.MaxBufferSize()) { | |
| 29 | 1 | throw DeviceError(DeviceErrorKind::Unsupported, | |
| 30 | 4 | std::to_string(size) + " bytes of " + name_ + | |
| 31 | 3 | " exceed the largest buffer the device can address"); | |
| 32 | } | ||
| 33 | 781 | size = std::max<std::uint64_t>(size, 1); | |
| 34 | 781 | const VmaVirtualAllocationCreateInfo info{.size = size, .alignment = alignment}; | |
| 35 | 1568 | const auto take = [&](std::uint32_t index) -> std::optional<BufferRange> { | |
| 36 | 787 | Arena& arena = *arenas_[index]; | |
| 37 | 787 | VmaVirtualAllocation allocation = VK_NULL_HANDLE; | |
| 38 | 787 | VkDeviceSize offset = 0; | |
| 39 | 787 | if (vmaVirtualAllocate(arena.block, &info, &allocation, &offset) != VK_SUCCESS) { | |
| 40 | 6 | return std::nullopt; | |
| 41 | } | ||
| 42 | 781 | arena.allocations.emplace(offset, allocation); | |
| 43 | 781 | arena.used += size; | |
| 44 | 781 | return BufferRange{index, offset, size}; | |
| 45 | 781 | }; | |
| 46 | 1665 | const auto takeAny = [&]() -> std::optional<BufferRange> { | |
| 47 | 890 | for (std::uint32_t index = 0; index < arenas_.size(); ++index) { | |
| 48 | 684 | if (const std::optional<BufferRange> range = take(index)) { | |
| 49 | 678 | return range; | |
| 50 | } | ||
| 51 | } | ||
| 52 | 206 | return std::nullopt; | |
| 53 | 781 | }; | |
| 54 | 781 | { | |
| 55 | 781 | const std::lock_guard lock(mutex_); | |
| 56 | 781 | if (const std::optional<BufferRange> range = takeAny()) { | |
| 57 | 678 | return *range; | |
| 58 | } | ||
| 59 | 678 | } | |
| 60 | // Ranges freed earlier come back once their work is done; collected without the lock, as | ||
| 61 | // giving them back takes it. | ||
| 62 | 103 | context_.Collect(); | |
| 63 | 103 | const std::lock_guard lock(mutex_); | |
| 64 | 103 | if (const std::optional<BufferRange> range = takeAny()) { | |
| 65 | ✗ | return *range; | |
| 66 | } | ||
| 67 | // A range larger than the arena size gets an arena of its own. | ||
| 68 | 103 | const std::uint64_t own = std::max(size, arenaSize_); | |
| 69 | 103 | if (const std::optional<BufferRange> range = take(AddArena(own))) { | |
| 70 | 103 | return *range; | |
| 71 | } | ||
| 72 | ✗ | throw DeviceError(DeviceErrorKind::Failed, "Allocating " + std::to_string(size) + " bytes of " + | |
| 73 | ✗ | name_ + " at alignment " + | |
| 74 | ✗ | std::to_string(alignment)); | |
| 75 | 103 | } | |
| 76 | |||
| 77 | 2015 | const Buffer& Arenas::Storage(const BufferRange& range) const { | |
| 78 | 2015 | const std::lock_guard lock(mutex_); | |
| 79 | 2015 | return arenas_[range.arena]->buffer; | |
| 80 | 2015 | } | |
| 81 | |||
| 82 | 735 | void Arenas::Free(const BufferRange& range) { | |
| 83 | 1470 | context_.Retire([this, range] { Release(range); }); | |
| 84 | 735 | } | |
| 85 | |||
| 86 | 3 | std::uint64_t Arenas::Used() const { | |
| 87 | 3 | const std::lock_guard lock(mutex_); | |
| 88 | 3 | std::uint64_t used = 0; | |
| 89 | 6 | for (const auto& arena : arenas_) { | |
| 90 | 3 | used += arena->used; | |
| 91 | } | ||
| 92 | 3 | return used; | |
| 93 | 3 | } | |
| 94 | |||
| 95 | 103 | std::uint32_t Arenas::AddArena(std::uint64_t size) { | |
| 96 | 103 | auto arena = std::make_unique<Arena>(); | |
| 97 | 206 | arena->buffer = | |
| 98 | 103 | Buffer(context_, size, use_, name_ + " arena " + std::to_string(arenas_.size())); | |
| 99 | 103 | const VmaVirtualBlockCreateInfo block{.size = size}; | |
| 100 | 309 | CheckVk(vmaCreateVirtualBlock(&block, &arena->block), "Creating " + name_ + "'s arena"); | |
| 101 | 103 | arenas_.push_back(std::move(arena)); | |
| 102 | 103 | return static_cast<std::uint32_t>(arenas_.size() - 1); | |
| 103 | 103 | } | |
| 104 | |||
| 105 | 735 | void Arenas::Release(const BufferRange& range) { | |
| 106 | 735 | const std::lock_guard lock(mutex_); | |
| 107 | 735 | Arena& arena = *arenas_[range.arena]; | |
| 108 | 735 | const auto found = arena.allocations.find(range.offset); | |
| 109 | 735 | if (found != arena.allocations.end()) { | |
| 110 | 735 | vmaVirtualFree(arena.block, found->second); | |
| 111 | 735 | arena.allocations.erase(found); | |
| 112 | 735 | arena.used -= range.size; | |
| 113 | } | ||
| 114 | 735 | } | |
| 115 | |||
| 116 | } // namespace ernie | ||
| 117 |