Skip to main content
Titles just need the purpose of the code
Source Link
Jamal
  • 34.9k
  • 13
  • 133
  • 237

C++ traversable Traversable memory pool, any obvious or glaring issues?

Specifically, it provides in-order traversal of the allocated data blocks, to improve cache effectiveness. Both allocation and deallocation are O(1)\$O(1)\$ operations. Traversal of the allocated blocks is linear in the number of allocated blocks, with a one-time potentially full traversal of the pool upon the first call to begin()begin() after (de)allocation.

Any suggestions? Obvious issues? Glaring mistakes? I'd love to hear them :) Any suggestions? Obvious issues? Glaring mistakes?

C++ traversable memory pool, any obvious or glaring issues?

Specifically, it provides in-order traversal of the allocated data blocks, to improve cache effectiveness. Both allocation and deallocation are O(1) operations. Traversal of the allocated blocks is linear in the number of allocated blocks, with a one-time potentially full traversal of the pool upon the first call to begin() after (de)allocation.

Any suggestions? Obvious issues? Glaring mistakes? I'd love to hear them :)

Traversable memory pool

Specifically, it provides in-order traversal of the allocated data blocks, to improve cache effectiveness. Both allocation and deallocation are \$O(1)\$ operations. Traversal of the allocated blocks is linear in the number of allocated blocks, with a one-time potentially full traversal of the pool upon the first call to begin() after (de)allocation.

Any suggestions? Obvious issues? Glaring mistakes?

Source Link

C++ traversable memory pool, any obvious or glaring issues?

I wrote this memory pool as a code sample for a job interview. It provides a per-class memory pool that can offer faster runtime performance for classes that need to be traversable and (de)allocatable during runtime.

Specifically, it provides in-order traversal of the allocated data blocks, to improve cache effectiveness. Both allocation and deallocation are O(1) operations. Traversal of the allocated blocks is linear in the number of allocated blocks, with a one-time potentially full traversal of the pool upon the first call to begin() after (de)allocation.

The pool stores metadata about the (un)allocated data in two structures both store in a singly linked list. The list of free blocks behaves like a stack: upon an allocation request a single block is popped off the front and upon deallocation a single block is pushed onto it. The list of allocated blocks functions as a singly linked list, and is mainly used for efficient traversal.

Any suggestions? Obvious issues? Glaring mistakes? I'd love to hear them :)

#pragma once

#include <iostream>
#include <iterator>
#include <cassert>

template<class T>
class Pool {

    friend class iterator;

    private:
        struct Node {
            T* data;
            Node* next;
            bool alloc;
        };

        unsigned int size;              ///< The total number of instances that fit in the pool
        unsigned int nrOfAllocs;        ///< How many instances have been allocated
        T* pool;                        ///< Starting address of the data block
        Node* metadata;                 ///< Storage for the free and allocated block list
        Node* firstFree, *firstAlloc;   ///< Pointers to the first blocks of both lists
        bool dirty;                     ///< Flag that is set upon (de)allocation, indicates that cleanup() should be called
        static Node EndNode;            ///< Used by the iterator

    public:
        class iterator : public std::iterator<std::forward_iterator_tag, T> {

            friend class Pool;

            public:
                bool operator == (iterator const& rhs) const {return node->data == (*rhs);}
                bool operator != (iterator const& rhs) const {return node->data != (*rhs);}
                bool operator <  (iterator const& rhs) const {return node->data < (*rhs);}
                bool operator <= (iterator const& rhs) const {return node->data <= (*rhs);}
                bool operator >  (iterator const& rhs) const {return node->data > (*rhs);}
                bool operator >= (iterator const& rhs) const {return node->data >= (*rhs);}
                iterator operator ++ () {
                    node = node->next == nullptr ? &Pool::EndNode : node->next;
                    return (*this);
                }
                iterator operator ++ (int){
                    iterator original(*this);
                    node = node->next == nullptr ? &Pool::EndNode : node->next;
                    return original;
                }

                T* operator * () const {
                    return node->data;
                }

            private:
                iterator(Pool::Node* node):node(node){}
                Pool::Node* node;
        };
        
        class const_iterator : public std::iterator<std::forward_iterator_tag, const T> {

            friend class Pool;

            public:
                bool operator == (const_iterator const& rhs) const {return node->data == (*rhs);}
                bool operator != (const_iterator const& rhs) const {return node->data != (*rhs);}
                bool operator <  (const_iterator const& rhs) const {return node->data < (*rhs);}
                bool operator <= (const_iterator const& rhs) const {return node->data <= (*rhs);}
                bool operator >  (const_iterator const& rhs) const {return node->data > (*rhs);}
                bool operator >= (const_iterator const& rhs) const {return node->data >= (*rhs);}
                const_iterator operator ++ () {
                    node = node->next == nullptr ? &Pool::EndNode : node->next;
                    return (*this);
                }
                const_iterator operator ++ (int){
                    const_iterator original(*this);
                    node = node->next == nullptr ? &Pool::EndNode : node->next;
                    return original;
                }

                const T* operator * () const {
                    return node->data;
                }

            private:
                const_iterator(Pool::Node* node):node(node){}
                Pool::Node* node;
        };

        /** \note Calling begin() triggers a potentially full traversal of the pool after a (de)allocation. */
        iterator begin() {
            cleanup();
            if ( firstAlloc == nullptr )
                return iterator(&Pool<T>::EndNode);
            return iterator(firstAlloc);
        }

        iterator end() const {
            return iterator(&Pool<T>::EndNode);
        }
        
        
        /** \param size Integer representing the number of instances that should be able to fit in the pool. */
        Pool(unsigned int size = 1024):size(size),nrOfAllocs(0),dirty(false),firstAlloc(nullptr) {
            
            // Allocate pool and metadata
            pool = static_cast<T*>(malloc(size * sizeof(T)));
            metadata = new Node[size];
            firstFree = &metadata[0];
            assert(firstFree != nullptr);

            // Initialise nodes
            for ( int i = 0; i < size; ++i ) {
                metadata[i].data = pool + i;
                metadata[i].next = &metadata[i+1];
                assert(metadata[i].next != nullptr);
                metadata[i].alloc = false;
            }
            metadata[size-1].next = nullptr;

            // Setup end node
            Pool::EndNode.data = pool + size;
        }

        ~Pool() {
            free(pool);
            delete[] metadata;
        }

        /// Called by new. Pops a free block, marks it and returns the associated pool address
        T* alloc() {

            // Check if we have free blocks left
            if ( firstFree == nullptr ) {
                std::cout << "Pool out of memory!" << std::endl;
                abort();
            }

            // Pop front free node
            Node* n = firstFree;
            firstFree = n->next;

            // Set metadata
            n->alloc = true;
            ++nrOfAllocs;
            dirty = true;

            // Fix head
            if ( firstAlloc == nullptr || firstAlloc->data > n->data )
                firstAlloc = n;

            return n->data;

        }

        /// Called by delete. Marks the block as unused, and pushes it on the free list.
        void free(void* block) {
        
            // Get node
            T* data = static_cast<T*>(block);
            int i = data - pool;
            assert(i >= 0 && i < size);
            Node* n = &metadata[i];
            assert(n->data == block);
            
            // Fix head
            if ( firstAlloc == n )
                firstAlloc = n->next;

            // Return node to free list
            n->next = firstFree;
            firstFree = n;

            // Set metadata
            n->alloc = false;
            --nrOfAllocs;
            dirty = true;
        }

        /// Called by begin if the pool is marked as dirty. Recreates the linked list for allocated blocks for efficient traversal.
        void cleanup() {

            if ( dirty && nrOfAllocs > 0 ) {

                int allocsFound = 0;
                int i = firstAlloc->data - pool;
                Node* n = firstAlloc;
                
                // Only search within the range we know has allocated blocks
                while ( allocsFound < nrOfAllocs && (i+1) < size ) {

                    if ( metadata[i+1].alloc ) {
                        ++allocsFound;
                        n->next = &metadata[i+1];
                        assert(n->next != nullptr);
                        n = &metadata[i+1];
                    }
                    ++i;
                }
                
                n->next = nullptr;
                dirty = false;

            }
        }
};

template <class T>
typename Pool<T>::Node Pool<T>::EndNode = {nullptr, nullptr, false};