-
Notifications
You must be signed in to change notification settings - Fork 2
Implement Import/Export functions #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8735bbc
894039a
71069b0
2d0eb3d
fe5744a
f6ae894
d98833e
a88e46e
ea5345a
9390b2e
92c35d7
4f4426a
7133329
203d4ba
998b576
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,6 +40,19 @@ | |
| #include <stack> | ||
| #include <vector> | ||
|
|
||
| template <typename T, size_t BlockSize> | ||
| struct ExportedAlloc | ||
| { | ||
| using pointer = T*; | ||
| using size_type = std::size_t; | ||
|
|
||
| // Free slots in the block | ||
| std::stack<pointer, std::vector<pointer>> free_slots; | ||
|
|
||
| // Memory blocks - Optional, only used in _export_all and _import | ||
| std::vector<pointer> memory_blocks; | ||
| }; | ||
|
|
||
| template <typename T, size_t BlockSize = 4096> | ||
| class PoolAllocator | ||
| { | ||
|
|
@@ -55,35 +68,35 @@ class PoolAllocator | |
| using size_type = std::size_t; | ||
| using difference_type = std::ptrdiff_t; | ||
| using propagate_on_container_copy_assignment = std::false_type; | ||
| using propagate_on_container_move_assignment = std::true_type; | ||
| using propagate_on_container_move_assignment = std::false_type; | ||
| using propagate_on_container_swap = std::true_type; | ||
| using is_always_equal = std::true_type; | ||
| using is_always_equal = std::false_type; | ||
canlin-zhang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /* Legacy Rebind struct */ | ||
| template <typename U> | ||
| struct rebind | ||
| { | ||
| typedef PoolAllocator<U, BlockSize> other; | ||
| }; | ||
| // /* Legacy Rebind struct */ | ||
| // template <typename U> | ||
| // struct rebind | ||
| // { | ||
| // typedef PoolAllocator<U, BlockSize> other; | ||
| // }; | ||
|
|
||
| /* Member functions */ | ||
| // Default constructor | ||
| PoolAllocator() noexcept; | ||
| // Copy constructor | ||
| PoolAllocator(const PoolAllocator& other) noexcept; | ||
| // Move constructor | ||
| PoolAllocator(PoolAllocator&& other) noexcept; | ||
| // Templated copy | ||
| // No Copy constructor | ||
| PoolAllocator(const PoolAllocator& other) = delete; | ||
| // No Move constructor | ||
| PoolAllocator(PoolAllocator&& other) = delete; | ||
| // No Templated copy | ||
| template <class U> | ||
| PoolAllocator(const PoolAllocator<U, BlockSize>& other) noexcept; | ||
| PoolAllocator(const PoolAllocator<U, BlockSize>& other) = delete; | ||
| // Destructor | ||
| ~PoolAllocator() noexcept; | ||
|
|
||
| // Assignment operator | ||
| // We do not allow copy assignment for allocators | ||
| PoolAllocator& operator=(const PoolAllocator& other) = delete; | ||
| // Move assignment operator | ||
| PoolAllocator& operator=(PoolAllocator&& other) noexcept; | ||
| // We do not allow move assignment for allocators | ||
| PoolAllocator& operator=(PoolAllocator&& other) = delete; | ||
|
|
||
| // Address functions | ||
| pointer addressof(reference x) const noexcept; | ||
|
|
@@ -126,30 +139,68 @@ class PoolAllocator | |
| // Delete an object | ||
| void delete_object(pointer p); | ||
|
|
||
| // Debug helper functions | ||
| // Get total allocated size | ||
| inline size_type total_allocated_size() const noexcept | ||
| { | ||
| return memory_blocks.size() * BlockSize; | ||
| } | ||
|
|
||
| // Get total number of free slots | ||
| // Does not account for partial blocks | ||
canlin-zhang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| inline size_type total_free_slots() const noexcept | ||
| { | ||
| return free_slots.size(); | ||
| } | ||
|
|
||
| // Transfer free slots from another allocator | ||
| void transfer_free(PoolAllocator<T, BlockSize>& from); | ||
| // Transfer all memory blocks and free slots from another allocator | ||
| void transfer_all(PoolAllocator<T, BlockSize>& from); | ||
|
|
||
| private: | ||
| // Allocate a memory block | ||
| void allocateBlock(); | ||
|
|
||
| // Allocator import/export functions | ||
| // Export | ||
| //! Export only the available slots as a vector of pointers. | ||
| //! Warning: This does NOT transfer ownership of the underlying memory blocks. | ||
| //! Do NOT use this function in threads with shorter lifetimes than other threads | ||
| //! accessing objects backed by this allocator. Doing so may lead to use-after-free. | ||
|
Comment on lines
+167
to
+170
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good warning. It may be useful to add to README.md some text that details the intended use of these functions.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. Let me change that in the README.md docs. |
||
| ExportedAlloc<T, BlockSize> _export_free(); | ||
|
|
||
| //! Export all the memory blocks + available slots | ||
| ExportedAlloc<T, BlockSize> _export_all(); | ||
|
|
||
| // Import | ||
| //! Import all memory blocks and free slots from an ExportedAlloc | ||
| void _import(ExportedAlloc<T, BlockSize>& exported); | ||
|
|
||
| // Pointer to blocks of memory | ||
| std::vector<pointer> memory_blocks; | ||
| size_type current_block_slot = 0; // Current slot in the current block | ||
|
|
||
| // Free list | ||
| std::stack<pointer, std::vector<pointer>> free_slots; | ||
|
|
||
| // Number of items in one block (will be set by the constructor only) | ||
| size_type num_items; | ||
| size_type item_size; | ||
| }; | ||
|
|
||
| // Operators | ||
| // Operator != and == | ||
| template <typename T1, size_t B1, typename T2, size_t B2> | ||
| // Only two references to the same allocator are equal | ||
canlin-zhang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| template <typename T, size_t BlockSize> | ||
| inline bool | ||
| operator==(const PoolAllocator<T1, B1>&, const PoolAllocator<T2, B2>&) noexcept | ||
| operator==(const PoolAllocator<T, BlockSize>& a, const PoolAllocator<T, BlockSize>& b) noexcept | ||
| { | ||
| return B1 == B2; | ||
| return &a == &b; | ||
| } | ||
|
|
||
| template <typename T1, size_t B1, typename T2, size_t B2> | ||
| template <typename T, size_t BlockSize> | ||
| inline bool | ||
| operator!=(const PoolAllocator<T1, B1>& a, const PoolAllocator<T2, B2>& b) noexcept | ||
| operator!=(const PoolAllocator<T, BlockSize>& a, const PoolAllocator<T, BlockSize>& b) noexcept | ||
| { | ||
| return !(a == b); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,91 +38,132 @@ | |
| template <typename T, size_t BlockSize> | ||
| PoolAllocator<T, BlockSize>::PoolAllocator() noexcept | ||
| { | ||
| // Calculate item alignment | ||
| constexpr size_type items_per_block = BlockSize / sizeof(T); | ||
canlin-zhang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| static_assert(items_per_block > 0, "Block size is too small for the type T"); | ||
| this->num_items = items_per_block; | ||
| this->item_size = sizeof(T); | ||
| } | ||
|
|
||
| // Copy constructor | ||
| // Destructor | ||
| template <typename T, size_t BlockSize> | ||
| PoolAllocator<T, BlockSize>::PoolAllocator(const PoolAllocator& other) noexcept | ||
| PoolAllocator<T, BlockSize>::~PoolAllocator() noexcept | ||
| { | ||
| // Nothing should be done here | ||
| // Free all memory blocks | ||
| for (pointer block : memory_blocks) | ||
| { | ||
| ::operator delete(block, std::align_val_t(alignof(T))); | ||
| } | ||
| } | ||
|
|
||
| // Move constructor | ||
| // Address functions | ||
| template <typename T, size_t BlockSize> | ||
| PoolAllocator<T, BlockSize>::PoolAllocator(PoolAllocator&& other) noexcept | ||
| typename PoolAllocator<T, BlockSize>::pointer | ||
| PoolAllocator<T, BlockSize>::addressof(reference x) const noexcept | ||
| { | ||
| // Move the memory blocks and free slots from the other allocator | ||
| memory_blocks = std::move(other.memory_blocks); | ||
| free_slots = std::move(other.free_slots); | ||
| current_block_slot = other.current_block_slot; | ||
| return std::addressof(x); | ||
| } | ||
|
|
||
| // Clear other allocator's states | ||
| other.current_block_slot = 0; | ||
| template <typename T, size_t BlockSize> | ||
| typename PoolAllocator<T, BlockSize>::const_pointer | ||
| PoolAllocator<T, BlockSize>::addressof(const_reference x) const noexcept | ||
| { | ||
| return std::addressof(x); | ||
| } | ||
|
|
||
| // Templated copy | ||
| template <typename T, size_t BlockSize> | ||
| template <class U> | ||
| PoolAllocator<T, BlockSize>::PoolAllocator(const PoolAllocator<U, BlockSize>& other) noexcept | ||
| inline ExportedAlloc<T, BlockSize> | ||
| PoolAllocator<T, BlockSize>::_export_free() | ||
| { | ||
| // Nothing should be done here | ||
| ExportedAlloc<T, BlockSize> exported; | ||
| exported.free_slots = std::move(free_slots); | ||
| // Clear the free slots stack | ||
| free_slots = std::stack<pointer, std::vector<pointer>>(); | ||
|
|
||
| // No memory blocks to export | ||
| exported.memory_blocks = std::vector<pointer>(); | ||
|
|
||
| return exported; | ||
| } | ||
|
|
||
| // Destructor | ||
| template <typename T, size_t BlockSize> | ||
| PoolAllocator<T, BlockSize>::~PoolAllocator() noexcept | ||
| ExportedAlloc<T, BlockSize> | ||
| PoolAllocator<T, BlockSize>::_export_all() | ||
| { | ||
| // Free all memory blocks | ||
| for (pointer block : memory_blocks) | ||
| ExportedAlloc<T, BlockSize> exported; | ||
| // Before moving the free slots, unwind the partially free bump-allocation block | ||
| // Add its free slots to the exported free slots | ||
| if (!memory_blocks.empty() && current_block_slot < num_items) | ||
| { | ||
| ::operator delete(block, std::align_val_t(alignof(T))); | ||
| // Convert the partially free (bump allocated) blocks to free slots | ||
| for (size_type i = current_block_slot; i < num_items; ++i) | ||
| { | ||
| // Push the pointer to the free slots stack | ||
| exported.free_slots.push(memory_blocks.back() + i); | ||
| } | ||
| } | ||
| // Append existing free slots to the unwinded free slots | ||
| exported.free_slots.c.insert(exported.free_slots.c.end(), free_slots.c.begin(), | ||
| free_slots.c.end()); | ||
eanorige marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Clear the free slots stack | ||
| free_slots = std::stack<pointer, std::vector<pointer>>(); | ||
|
|
||
| // Move memory blocks to the exported struct | ||
| exported.memory_blocks = std::move(memory_blocks); | ||
| // Clear the memory blocks (don't free them) | ||
| memory_blocks = std::vector<pointer>(); | ||
|
|
||
| // Reset the current block slot in the allocator | ||
| current_block_slot = 0; | ||
|
|
||
| return exported; | ||
| } | ||
|
|
||
| // Move assignment operator | ||
| template <typename T, size_t BlockSize> | ||
| PoolAllocator<T, BlockSize>& | ||
| PoolAllocator<T, BlockSize>::operator=(PoolAllocator&& other) noexcept | ||
| void | ||
| PoolAllocator<T, BlockSize>::_import(ExportedAlloc<T, BlockSize>& exported) | ||
eanorige marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| // Append the free slots from the exported allocator | ||
| free_slots.c.insert(free_slots.c.end(), exported.free_slots.begin(), exported.free_slots.end()); | ||
|
|
||
| // Move the memory blocks and free slots from the other allocator | ||
| memory_blocks = std::move(other.memory_blocks); | ||
| free_slots = std::move(other.free_slots); | ||
| current_block_slot = other.current_block_slot; | ||
| // We don't need to change the current_block_slot here | ||
| // As the imported allocator's partially free slots are already accounted for during export | ||
|
|
||
| // Clear other allocator's states | ||
| other.current_block_slot = 0; | ||
| // Append imported memory blocks from the exported allocator | ||
| memory_blocks.insert(memory_blocks.end(), | ||
| std::make_move_iterator(exported.memory_blocks.begin()), | ||
| std::make_move_iterator(exported.memory_blocks.end())); | ||
|
Comment on lines
+134
to
+135
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, why the move iterators for copying pointers? It's sort-of correct, as our goal is re-homing ownership of these pointers, but I bet it'll cost some compile time and not improve generated code.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, I think begin() and end() already serve the purpose well. |
||
|
|
||
| return *this; | ||
| // Clear the imported memory blocks | ||
| exported.memory_blocks = std::vector<pointer>(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assignment to an empty vector isn't the right way to clear this; better is the
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait - really? I thought std::move already set the T&& t to an undefined state.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but continuing to use a moved-from value has no guaranteed behavior, so it's necessary to clear it so you know what state the vector is in. |
||
| } | ||
|
|
||
| // Address functions | ||
| template <typename T, size_t BlockSize> | ||
| typename PoolAllocator<T, BlockSize>::pointer | ||
| PoolAllocator<T, BlockSize>::addressof(reference x) const noexcept | ||
| void | ||
| PoolAllocator<T, BlockSize>::transfer_all(PoolAllocator<T, BlockSize>& from) | ||
| { | ||
| return std::addressof(x); | ||
| assert(&from != this && "Cannot import directly from self"); | ||
|
|
||
| // Export and Import the free slots | ||
| auto exported = from._export_all(); | ||
| _import(exported); | ||
eanorige marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| template <typename T, size_t BlockSize> | ||
| typename PoolAllocator<T, BlockSize>::const_pointer | ||
| PoolAllocator<T, BlockSize>::addressof(const_reference x) const noexcept | ||
| void | ||
| PoolAllocator<T, BlockSize>::transfer_free(PoolAllocator<T, BlockSize>& from) | ||
| { | ||
| return std::addressof(x); | ||
| assert(&from != this && "Cannot import directly from self"); | ||
|
|
||
| // Export and Import the free slots | ||
| auto exported = from._export_free(); | ||
| _import(exported); | ||
| } | ||
|
|
||
| template <typename T, size_t BlockSize> | ||
| void | ||
| PoolAllocator<T, BlockSize>::allocateBlock() | ||
| { | ||
| // Calculate item alignment | ||
| constexpr size_type num_items = BlockSize / sizeof(T); | ||
|
|
||
| if (num_items < 1) | ||
| { | ||
| throw std::bad_alloc(); | ||
| } | ||
|
|
||
| // Allocate a new block of memory | ||
| pointer new_block = | ||
| reinterpret_cast<pointer>(::operator new(BlockSize, std::align_val_t(alignof(T)))); | ||
|
|
@@ -152,7 +193,7 @@ PoolAllocator<T, BlockSize>::allocate(size_type n) | |
| // Handle single object allocation | ||
| else | ||
| { | ||
| constexpr size_type num_items = BlockSize / sizeof(T); | ||
| constexpr size_type items_per_block = BlockSize / sizeof(T); | ||
|
|
||
| // Check free slots first | ||
| if (!free_slots.empty()) | ||
|
|
@@ -163,7 +204,7 @@ PoolAllocator<T, BlockSize>::allocate(size_type n) | |
| return p; | ||
| } | ||
| // Check current block slot | ||
| else if (!memory_blocks.empty() && current_block_slot < num_items) | ||
| else if (!memory_blocks.empty() && current_block_slot < items_per_block) | ||
| { | ||
| // Increment by 1 | ||
| pointer p = memory_blocks.back() + current_block_slot; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.