Skip to content

Commit ea59336

Browse files
committed
Temp: Dramatically speed up allocator by using standard new() and delete(). TODO: Verify that this is not UB
1 parent 78be4f7 commit ea59336

File tree

2 files changed

+37
-57
lines changed

2 files changed

+37
-57
lines changed

include/pscript/memory.hpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ class memory_pool {
109109
std::size_t mem_size = 0;
110110

111111
static constexpr inline std::size_t min_block_size = 16;
112-
static constexpr inline std::size_t small_block_cache_size = 32;
112+
static constexpr inline std::size_t small_block_cache_size = 64;
113+
static constexpr inline std::size_t max_consecutive_merges = 4;
113114

114115
/**
115116
* @brief Represents a block in the buddy allocator.
@@ -131,6 +132,9 @@ class memory_pool {
131132
std::unique_ptr<block> root_block = nullptr;
132133

133134
std::array<block*, small_block_cache_size> small_block_cache {};
135+
// fast lookup for blocks that are allocated.
136+
// TODO: possibly use faster hashmap if this becomes a bottleneck again
137+
std::unordered_map<ps::pointer, block*> allocated_block_lookup {};
134138
std::size_t num_small_blocks = 0;
135139

136140
// Finds a block to allocate with given size, starting at a given root block.
@@ -139,7 +143,7 @@ class memory_pool {
139143

140144
// Descends the tree to find the block holding the pointer and frees this block.
141145
// Possibly also merges it with its buddy.
142-
bool free_block(block* root, block* parent, ps::pointer ptr);
146+
bool free_block(ps::pointer ptr);
143147

144148
// Divides a block into two buddies.
145149
// If the block is already the minimum size, this does nothing and returns false, otherwise it returns true.

src/pscript/memory.cpp

Lines changed: 31 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,24 @@ memory_pool::memory_pool(std::size_t size) {
5252
}
5353

5454
[[nodiscard]] pointer memory_pool::allocate(std::size_t bytes) {
55+
return reinterpret_cast<pointer>(new std::byte[bytes]);
5556
// Compute the smallest possible block size that would fit this allocation
5657
std::size_t const block_size = std::max(min_block_size, plib::next_pow_two(bytes));
5758
// Find a block, possibly subdividing blocks
5859
block* block = find_block(root_block.get(), block_size);
5960
// If no block was found, return a null pointer
6061
if (!block) return null_pointer;
62+
// Add the block to the allocated block cache, so it can be found easily when freeing
63+
allocated_block_lookup[block->ptr] = block;
6164
// Mark it as allocated and return the pointer
6265
block->free = false;
6366
return block->ptr;
6467
}
6568

6669
void memory_pool::free(ps::pointer ptr) {
6770
if (!verify_pointer(ptr)) return;
68-
69-
free_block(root_block.get(), nullptr, ptr);
71+
delete[] decode_pointer(ptr);
72+
free_block(ptr);
7073
}
7174

7275
[[nodiscard]] bool memory_pool::verify_pointer(ps::pointer ptr) const noexcept {
@@ -109,64 +112,34 @@ void memory_pool::verify_pointer_throw(ps::pointer ptr) const {
109112
return find_block(root->right.get(), block_size);
110113
}
111114

112-
bool memory_pool::free_block(block* root, block* parent, ps::pointer ptr) {
115+
bool memory_pool::free_block(ps::pointer ptr) {
113116
// We need to find the block holding the allocated pointer.
114117
// This is going to be the block with the free flag on false, and holding this pointer.
115118

116-
if (!root) return false;
117-
118-
// Found matching pointer. This will be the allocated block if it has no child buddies.
119-
if (root->ptr == ptr && !root->left && !root->right) {
120-
// Mark the block as free
121-
root->free = true;
122-
// Zero out block memory
123-
std::memset(decode_pointer(root->ptr), 0, root->size);
124-
// If this was a small block, and there is space left in the small block cache, add it to the cache.
125-
if (root->size == min_block_size && num_small_blocks != small_block_cache.size()) {
126-
small_block_cache[num_small_blocks++] = root;
127-
}
128-
// If there is a parent, check if both left and right of it are free
129-
if (parent && parent->left->free && parent->right->free) {
130-
// If so, merge those blocks
131-
bool success = merge_blocks(parent);
132-
// Something went wrong
133-
if (!success) return false;
134-
}
135-
return true; // freed a block, return true
136-
137-
} else {
138-
// This isn't the block holding the allocation, try to free in left and right
139-
140-
// Has no children, early exit.
141-
if (!root->left || !root->right) return false;
142-
143-
// We can make a small optimization by comparing the pointer with the pointer of the right child.
144-
if (ptr < root->right->ptr) {
145-
// Block must be in left child
146-
bool free = free_block(root->left.get(), root, ptr);
147-
148-
// Once again try to merge
149-
if (free && parent && parent->free && parent->left->free && parent->right->free) {
150-
bool success = merge_blocks(parent);
151-
if (!success) return false;
152-
}
153-
154-
return free;
155-
} else {
156-
// Block must be in right child
157-
bool free = free_block(root->right.get(), root, ptr);
158-
159-
// Once again try to merge
160-
if (free && parent && parent->free && parent->left->free && parent->right->free) {
161-
bool success = merge_blocks(parent);
162-
if (!success) return false;
163-
}
119+
block* b = allocated_block_lookup.at(ptr);
120+
// Mark the block as free
121+
b->free = true;
122+
// Zero out block memory
123+
std::memset(decode_pointer(ptr), 0, b->size);
124+
// If this was a small block, and there is space left in the small block cache, add it to the cache.
125+
if (b->size == min_block_size && num_small_blocks != small_block_cache.size()) {
126+
small_block_cache[num_small_blocks++] = b;
127+
}
128+
// If there is a parent, check if both left and right of it are free.
129+
// We repeat this process up the tree as long as we can
130+
block* cur = b;
131+
std::size_t merge_i = 0;
132+
// we limit the amount of merges to make sure we keep some smaller blocks around.
133+
while (merge_i++ < max_consecutive_merges && cur->parent && cur->parent->left->free && cur->parent->right->free) {
134+
// If so, merge those blocks
135+
block* parent = cur->parent;
136+
[[maybe_unused]] bool _ = merge_blocks(parent);
137+
cur = parent;
138+
}
164139

165-
return free;
166-
}
140+
allocated_block_lookup.erase(ptr);
167141

168-
PLIB_UNREACHABLE();
169-
}
142+
return true;
170143
}
171144

172145
[[nodiscard]] bool memory_pool::subdivide_block(block *b) {
@@ -219,12 +192,15 @@ bool memory_pool::free_block(block* root, block* parent, ps::pointer ptr) {
219192
}
220193

221194
[[nodiscard]] ps::byte* memory_pool::decode_pointer(ps::pointer ptr) {
195+
return reinterpret_cast<std::byte*>(ptr);
222196
// TODO: possibly add toggle to disable this
223197
verify_pointer_throw(ptr);
224198
return &memory[ptr];
225199
}
226200

227201
[[nodiscard]] ps::byte const* memory_pool::decode_pointer(ps::pointer ptr) const {
202+
return reinterpret_cast<std::byte*>(ptr);
203+
228204
verify_pointer_throw(ptr);
229205
return &memory[ptr];
230206
}

0 commit comments

Comments
 (0)