tint: Add a Count() method to BlockAllocator.

Tells you how many things you've allocated.
Will be used for various optimizations.

Change-Id: I8a31bb06e2b23781245bbfd16fabc9b85e440d14
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/96142
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
Ben Clayton 2022-07-15 23:42:14 +00:00 committed by Dawn LUCI CQ
parent 6a80ce6c66
commit db89595550
2 changed files with 96 additions and 73 deletions

View File

@ -24,10 +24,10 @@
namespace tint::utils {
/// A container and allocator of objects of (or deriving from) the template
/// type `T`. Objects are allocated by calling Create(), and are owned by the
/// BlockAllocator. When the BlockAllocator is destructed, all constructed
/// objects are automatically destructed and freed.
/// A container and allocator of objects of (or deriving from) the template type `T`.
/// Objects are allocated by calling Create(), and are owned by the BlockAllocator.
/// When the BlockAllocator is destructed, all constructed objects are automatically destructed and
/// freed.
///
/// Objects held by the BlockAllocator can be iterated over using a View.
template <typename T, size_t BLOCK_SIZE = 64 * 1024, size_t BLOCK_ALIGNMENT = 16>
@ -44,8 +44,7 @@ class BlockAllocator {
/// Block is linked list of memory blocks.
/// Blocks are allocated out of heap memory.
///
/// Note: We're not using std::aligned_storage here as this warns / errors
/// on MSVC.
/// Note: We're not using std::aligned_storage here as this warns / errors on MSVC.
struct alignas(BLOCK_ALIGNMENT) Block {
uint8_t data[BLOCK_SIZE];
Block* next;
@ -97,22 +96,22 @@ class BlockAllocator {
size_t idx;
};
/// View provides begin() and end() methods for looping over the objects
/// owned by the BlockAllocator.
/// View provides begin() and end() methods for looping over the objects owned by the
/// BlockAllocator.
template <bool IS_CONST>
class TView {
public:
/// @returns an iterator to the beginning of the view
TIterator<IS_CONST> begin() const {
return TIterator<IS_CONST>{allocator_->pointers_.root, 0};
return TIterator<IS_CONST>{allocator_->data.pointers.root, 0};
}
/// @returns an iterator to the end of the view
TIterator<IS_CONST> end() const {
return allocator_->pointers_.current_index >= Pointers::kMax
return allocator_->data.pointers.current_index >= Pointers::kMax
? TIterator<IS_CONST>(nullptr, 0)
: TIterator<IS_CONST>(allocator_->pointers_.current,
allocator_->pointers_.current_index);
: TIterator<IS_CONST>(allocator_->data.pointers.current,
allocator_->data.pointers.current_index);
}
private:
@ -128,12 +127,12 @@ class BlockAllocator {
/// An immutable iterator type over the objects of the BlockAllocator
using ConstIterator = TIterator<true>;
/// View provides begin() and end() methods for looping over the objects
/// owned by the BlockAllocator.
/// View provides begin() and end() methods for looping over the objects owned by the
/// BlockAllocator.
using View = TView<false>;
/// ConstView provides begin() and end() methods for looping over the objects
/// owned by the BlockAllocator.
/// ConstView provides begin() and end() methods for looping over the objects owned by the
/// BlockAllocator.
using ConstView = TView<true>;
/// Constructor
@ -141,10 +140,7 @@ class BlockAllocator {
/// Move constructor
/// @param rhs the BlockAllocator to move
BlockAllocator(BlockAllocator&& rhs) {
std::swap(block_, rhs.block_);
std::swap(pointers_, rhs.pointers_);
}
BlockAllocator(BlockAllocator&& rhs) { std::swap(data, rhs.data); }
/// Move assignment operator
/// @param rhs the BlockAllocator to move
@ -152,8 +148,7 @@ class BlockAllocator {
BlockAllocator& operator=(BlockAllocator&& rhs) {
if (this != &rhs) {
Reset();
std::swap(block_, rhs.block_);
std::swap(pointers_, rhs.pointers_);
std::swap(data, rhs.data);
}
return *this;
}
@ -168,8 +163,7 @@ class BlockAllocator {
ConstView Objects() const { return ConstView(this); }
/// Creates a new `TYPE` owned by the BlockAllocator.
/// When the BlockAllocator is destructed the object will be destructed and
/// freed.
/// When the BlockAllocator is destructed the object will be destructed and freed.
/// @param args the arguments to pass to the type constructor
/// @returns the pointer to the constructed object
template <typename TYPE = T, typename... ARGS>
@ -183,6 +177,7 @@ class BlockAllocator {
auto* ptr = Allocate<TYPE>();
new (ptr) TYPE(std::forward<ARGS>(args)...);
AddObjectPointer(ptr);
data.count++;
return ptr;
}
@ -192,97 +187,106 @@ class BlockAllocator {
for (auto ptr : Objects()) {
ptr->~T();
}
auto* block = block_.root;
auto* block = data.block.root;
while (block != nullptr) {
auto* next = block->next;
delete block;
block = next;
}
block_ = {};
pointers_ = {};
data = {};
}
/// @returns the total number of allocated objects.
size_t Count() const { return data.count; }
private:
BlockAllocator(const BlockAllocator&) = delete;
BlockAllocator& operator=(const BlockAllocator&) = delete;
/// Allocates an instance of TYPE from the current block, or from a newly
/// allocated block if the current block is full.
/// Allocates an instance of TYPE from the current block, or from a newly allocated block if the
/// current block is full.
template <typename TYPE>
TYPE* Allocate() {
static_assert(sizeof(TYPE) <= BLOCK_SIZE,
"Cannot construct TYPE with size greater than BLOCK_SIZE");
static_assert(alignof(TYPE) <= BLOCK_ALIGNMENT, "alignof(TYPE) is greater than ALIGNMENT");
block_.current_offset = utils::RoundUp(alignof(TYPE), block_.current_offset);
if (block_.current_offset + sizeof(TYPE) > BLOCK_SIZE) {
auto& block = data.block;
block.current_offset = utils::RoundUp(alignof(TYPE), block.current_offset);
if (block.current_offset + sizeof(TYPE) > BLOCK_SIZE) {
// Allocate a new block from the heap
auto* prev_block = block_.current;
block_.current = new Block;
if (!block_.current) {
auto* prev_block = block.current;
block.current = new Block;
if (!block.current) {
return nullptr; // out of memory
}
block_.current->next = nullptr;
block_.current_offset = 0;
block.current->next = nullptr;
block.current_offset = 0;
if (prev_block) {
prev_block->next = block_.current;
prev_block->next = block.current;
} else {
block_.root = block_.current;
block.root = block.current;
}
}
auto* base = &block_.current->data[0];
auto* ptr = utils::Bitcast<TYPE*>(base + block_.current_offset);
block_.current_offset += sizeof(TYPE);
auto* base = &block.current->data[0];
auto* ptr = utils::Bitcast<TYPE*>(base + block.current_offset);
block.current_offset += sizeof(TYPE);
return ptr;
}
/// Adds `ptr` to the linked list of objects owned by this BlockAllocator.
/// Once added, `ptr` will be tracked for destruction when the BlockAllocator
/// is destructed.
/// Once added, `ptr` will be tracked for destruction when the BlockAllocator is destructed.
void AddObjectPointer(T* ptr) {
if (pointers_.current_index >= Pointers::kMax) {
auto* prev_pointers = pointers_.current;
pointers_.current = Allocate<Pointers>();
if (!pointers_.current) {
auto& pointers = data.pointers;
if (pointers.current_index >= Pointers::kMax) {
auto* prev_pointers = pointers.current;
pointers.current = Allocate<Pointers>();
if (!pointers.current) {
return; // out of memory
}
pointers_.current->next = nullptr;
pointers_.current_index = 0;
pointers.current->next = nullptr;
pointers.current_index = 0;
if (prev_pointers) {
prev_pointers->next = pointers_.current;
prev_pointers->next = pointers.current;
} else {
pointers_.root = pointers_.current;
pointers.root = pointers.current;
}
}
pointers_.current->ptrs[pointers_.current_index++] = ptr;
pointers.current->ptrs[pointers.current_index++] = ptr;
}
struct {
/// The root block of the block linked list
Block* root = nullptr;
/// The current (end) block of the blocked linked list.
/// New allocations come from this block
Block* current = nullptr;
/// The byte offset in #current for the next allocation.
/// Initialized with BLOCK_SIZE so that the first allocation triggers a
/// block allocation.
size_t current_offset = BLOCK_SIZE;
} block_;
struct {
/// The root block of the block linked list
Block* root = nullptr;
/// The current (end) block of the blocked linked list.
/// New allocations come from this block
Block* current = nullptr;
/// The byte offset in #current for the next allocation.
/// Initialized with BLOCK_SIZE so that the first allocation triggers a block
/// allocation.
size_t current_offset = BLOCK_SIZE;
} block;
struct {
/// The root Pointers structure of the pointers linked list
Pointers* root = nullptr;
/// The current (end) Pointers structure of the pointers linked list.
/// AddObjectPointer() adds to this structure.
Pointers* current = nullptr;
/// The array index in #current for the next append.
/// Initialized with Pointers::kMax so that the first append triggers a
/// allocation of the Pointers structure.
size_t current_index = Pointers::kMax;
} pointers_;
struct {
/// The root Pointers structure of the pointers linked list
Pointers* root = nullptr;
/// The current (end) Pointers structure of the pointers linked list.
/// AddObjectPointer() adds to this structure.
Pointers* current = nullptr;
/// The array index in #current for the next append.
/// Initialized with Pointers::kMax so that the first append triggers a allocation of
/// the Pointers structure.
size_t current_index = Pointers::kMax;
} pointers;
size_t count = 0;
} data;
};
} // namespace tint::utils

View File

@ -33,6 +33,7 @@ TEST_F(BlockAllocatorTest, Empty) {
Allocator allocator;
EXPECT_EQ(allocator.Count(), 0u);
for (int* i : allocator.Objects()) {
(void)i;
if ((true)) { // Workaround for "error: loop will run at most once"
@ -47,6 +48,19 @@ TEST_F(BlockAllocatorTest, Empty) {
}
}
TEST_F(BlockAllocatorTest, Count) {
using Allocator = BlockAllocator<int>;
for (size_t n : {0u, 1u, 10u, 16u, 20u, 32u, 50u, 64u, 100u, 256u, 300u, 512u, 500u, 512u}) {
Allocator allocator;
EXPECT_EQ(allocator.Count(), 0u);
for (size_t i = 0; i < n; i++) {
allocator.Create(123);
}
EXPECT_EQ(allocator.Count(), n);
}
}
TEST_F(BlockAllocatorTest, ObjectLifetime) {
using Allocator = BlockAllocator<LifetimeCounter>;
@ -75,9 +89,11 @@ TEST_F(BlockAllocatorTest, MoveConstruct) {
allocator_a.Create(&count);
}
EXPECT_EQ(count, n);
EXPECT_EQ(allocator_a.Count(), n);
Allocator allocator_b{std::move(allocator_a)};
EXPECT_EQ(count, n);
EXPECT_EQ(allocator_b.Count(), n);
}
EXPECT_EQ(count, 0u);
@ -97,16 +113,19 @@ TEST_F(BlockAllocatorTest, MoveAssign) {
allocator_a.Create(&count_a);
}
EXPECT_EQ(count_a, n);
EXPECT_EQ(allocator_a.Count(), n);
Allocator allocator_b;
for (size_t i = 0; i < n; i++) {
allocator_b.Create(&count_b);
}
EXPECT_EQ(count_b, n);
EXPECT_EQ(allocator_b.Count(), n);
allocator_b = std::move(allocator_a);
EXPECT_EQ(count_a, n);
EXPECT_EQ(count_b, 0u);
EXPECT_EQ(allocator_b.Count(), n);
}
EXPECT_EQ(count_a, 0u);