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

View File

@ -33,6 +33,7 @@ TEST_F(BlockAllocatorTest, Empty) {
Allocator allocator; Allocator allocator;
EXPECT_EQ(allocator.Count(), 0u);
for (int* i : allocator.Objects()) { for (int* i : allocator.Objects()) {
(void)i; (void)i;
if ((true)) { // Workaround for "error: loop will run at most once" 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) { TEST_F(BlockAllocatorTest, ObjectLifetime) {
using Allocator = BlockAllocator<LifetimeCounter>; using Allocator = BlockAllocator<LifetimeCounter>;
@ -75,9 +89,11 @@ TEST_F(BlockAllocatorTest, MoveConstruct) {
allocator_a.Create(&count); allocator_a.Create(&count);
} }
EXPECT_EQ(count, n); EXPECT_EQ(count, n);
EXPECT_EQ(allocator_a.Count(), n);
Allocator allocator_b{std::move(allocator_a)}; Allocator allocator_b{std::move(allocator_a)};
EXPECT_EQ(count, n); EXPECT_EQ(count, n);
EXPECT_EQ(allocator_b.Count(), n);
} }
EXPECT_EQ(count, 0u); EXPECT_EQ(count, 0u);
@ -97,16 +113,19 @@ TEST_F(BlockAllocatorTest, MoveAssign) {
allocator_a.Create(&count_a); allocator_a.Create(&count_a);
} }
EXPECT_EQ(count_a, n); EXPECT_EQ(count_a, n);
EXPECT_EQ(allocator_a.Count(), n);
Allocator allocator_b; Allocator allocator_b;
for (size_t i = 0; i < n; i++) { for (size_t i = 0; i < n; i++) {
allocator_b.Create(&count_b); allocator_b.Create(&count_b);
} }
EXPECT_EQ(count_b, n); EXPECT_EQ(count_b, n);
EXPECT_EQ(allocator_b.Count(), n);
allocator_b = std::move(allocator_a); allocator_b = std::move(allocator_a);
EXPECT_EQ(count_a, n); EXPECT_EQ(count_a, n);
EXPECT_EQ(count_b, 0u); EXPECT_EQ(count_b, 0u);
EXPECT_EQ(allocator_b.Count(), n);
} }
EXPECT_EQ(count_a, 0u); EXPECT_EQ(count_a, 0u);