I3MUP1+Exercise+12

toc =Exercise 12 - Fixed Size Buffer Pattern= //In this exercise you will get acquainted with the Fixed Size Buffer design pattern that is useful for creating// //real-time heaps without fragmentation.//

This wiki is written using a provided source code solution as it's base.

Exercise 12.1 - Sketch
//Sketch the situation when the HeapManager is running and has created 6 memory pools, each with 5 blocks// //and with block size 8, 16, 32, 64, 128, and 256 bytes. Sketch what happens when someone requests 100// //bytes of memory. Sketch what happens when the memory block is returned to the HeapManager//

The hand drawn sketch below shows the situation where a HeapManager has 6 memory pools, each controlling 5 blocks(buffers) of memory of sizes 8-256 Bytes. When someone requests 100 bytes of memory, a pointer to the first available memory block of at least 100 bytes is returned (128 or 256 bytes) and the block will be marked as in use. If no blocks are free or big enough, an error will be returned. When the memory block is returned to the HeapManager, it will be marked as free for use once again.



Exercise 12.2 - MemPool
//Design and implement class MemPool//

The code below is the header file for MemPool and illustrates the design. MemPool contains the implementation of the BufferDescriptor class, and a BufferDescriptor pointer called BufPtrs which is an array for it's allocated memory blocks. code format="cpp" //===================================== // File       : MemPool.h // Created    : Oct 8, 2009 // Author   : tfj // Descr. : //=====================================

using namespace std;
 * 1) pragma once
 * 2) include
 * 3) include

class MemPool {   public: MemPool(size_t n, size_t s); ~MemPool; void* allocate; bool free(void* p); bool anyFree; void info; size_t getElementSize; private: class BufferDescriptor {           public: BufferDescriptor : ptr(NULL), isFree(false) {} void* ptr; bool isFree; };       void* rawMem; BufferDescriptor* bufPtrs; size_t nElements; size_t elementSize; size_t nElementsFree; }; code

Constructor/destructor
The constructor is shown below, and uses the 'void * calloc ( size_t num, size_t size )' function to allocate memory for an array with num elements of length size. First, a raw chunk of memory large enough to hold all the elements of the specified size is allocated. A rawMem pointer is used to point to this chunk of memory. Then an array of BufferDescriptor objects is allocated, and the elements of the BufPtrs array are each set to point to a BufferDescriptor object. All BufferDescriptor objects are marked as free. code format="cpp" MemPool::MemPool(unsigned int n, unsigned int s) : nElements(n), elementSize(s), nElementsFree(n) {   rawMem = calloc(nElements, elementSize); if(!rawMem) throw MemPoolException("raw mem alloc failed");

bufPtrs = (BufferDescriptor*)calloc(nElements, sizeof(BufferDescriptor)); if(!bufPtrs) throw MemPoolException("bufPtr alloc failed");

for(size_t i=0; i<nElements; i++) {       bufPtrs[i].ptr = (void*)((int)rawMem + (i*elementSize)); bufPtrs[i].isFree = true; } } code

The destructor uses the 'void free ( void * ptr )' function to deallocate all memory that was allocated by the constructor. code format="cpp" MemPool::~MemPool {   ::free(rawMem); ::free(bufPtrs); } code

allocate/free
The allocate function looks for the first free buffer block. When it finds a free block, the block is the marked as in use, and a pointer to the block is returned. If no blocks are free, an exception is thrown. code format="cpp" void* MemPool::allocate {   if(nElementsFree == 0) throw MemPoolException("MemPool allocate failed"); for(size_t i=0; i<nElements; i++) {       if(bufPtrs[i].isFree == true) {           bufPtrs[i].isFree = false; --nElementsFree; return bufPtrs[i].ptr;

}   }    throw MemPoolException("MemPool allocate failed"); } code

The free first checks if the memory to be freed is indeed from this Memory pool. This is done by comparing the parameter pointer with the corresponding BufPtrs pointer. If these are equal, the buffer block does belong to the memory pool. The isFree flag is set to true and the free function returns true upon success. If all buffers in this block are already free or if the buffer is not recognized as part of this pool, false is returned instead. code format="cpp" bool MemPool::free(void* p) { if(nElementsFree == nElements) return false;

for(size_t i=0; i<nElements; i++) {       if(bufPtrs[i].ptr == p)        { bufPtrs[i].isFree = true; ++nElementsFree; return true; }   }    return false; } code

Furthermore, the MemPool class contains a function to check if any buffers are free (anyFree), and an info function to output information about the memory pool.

Exercise 12.3 - HeapManager
//Design and implement class HeapManager//

The header file below illustrates the design behind the HeapManager class. It is a Singleton class, which means that only one of this kind of object can exist at any one time. The class contains an array of MemPool pointers. code format="cpp" //========================================= //FILE  : HeapManager.h //CREATED: jan 26, 2009 //AUTHOR : tfj // //DESCR. : //=========================================


 * 1) include"MemPool.h"

//============================ // CLASS   : HeapManager // DESCR. : //============================ class HeapManager { public: static HeapManager& getInstance; ~HeapManager; void* allocate(size_t n); void free(void* p); void info;

protected: HeapManager(size_t nPools, size_t nElements=10, size_t smallestPool=8);

private: static HeapManager instance; MemPool** memPools; size_t nPools; }; code

Constructor/destructor
The constructor uses malloc to allocate a raw block of memory big enough to hold all the MemPool objects. Then, "placement new" is used to allocate each of the single MemPool objects. "Placement new" means specifying where in the memory the new operator should place the newly created object. This is done in a for-loop by first using malloc to allocate space for the object, and then using the pointer returned by malloc for the "placement new" allocation of the MemPool object. For each iteration of the for-loop, the size of the MemPool object is doubled, starting at 8 bytes. code format="cpp" HeapManager::HeapManager(size_t nPools, size_t nElements, size_t smallestElementSize) :    nPools(nPools) {   void* vp;

memPools = (MemPool**)malloc(nPools*sizeof(MemPool*)); if(memPools==NULL) throw HeapManagerException("HeapManager::HeapManager - memPool alloc failed");

size_t size = smallestElementSize; for(size_t i=0; i < nPools; i++) {       vp = malloc(sizeof(MemPool));        // Allocate sufficient space for a MemPool object, and...        memPools[i] = new (vp) MemPool(nElements, size);    // ... use "object placement new" to initialize it

size *=2; } } code

The destructor explicitly calls the destructor for all the MemPool objects, since the "placement new" gives us this responsibility. Then, ::free is used to deallocate the area in the memory previously occupied by the MemPool object. Finally, the area in the memory previously used for holding all the MemPool objects is deallocated with ::free. code format="cpp" HeapManager::~HeapManager {   for(size_t i=0; i < nPools; i++) {       memPools[i]->MemPool::~MemPool; ::free(memPools[i]); }   ::free(memPools); } code

allocate/free
The allocate function finds the smallest available fitting block of memory from the MemPool objects, and calls allocate on that MemPool object which returns a pointer to the memory block. If no block is available, an exception is thrown. code format="cpp" void* HeapManager::allocate(size_t n) { for(size_t i=0; i < nPools; i++) {       if(n <=memPools[i]->getElementSize && memPools[i]->anyFree) return memPools[i]->allocate; }

throw HeapManagerException("Alloc failed"); } code

The free function attempts to return the memory to the MemPool it came from, by trying MemPool::free until the deallocation succeeds. If no MemPool objects are able to deallocate the memory, an exception is thrown. code format="cpp" void HeapManager::free(void* p) { for(size_t i=0; i < nPools; i++) {       if(memPools[i]->free(p)) return; }   throw HeapManagerException("Free failed"); } code

Besides these functions, the function info outputs information about the MemPool objects and their use.

Exercise 12.4 Overload new and delete
//Overload the global operator new and delete to use the HeapManager. new should allocate memory// //from the HeapManager and delete should return memory blocks to the HeapManager.//

The new and delete operators are overloaded globally to use the allocate and free functions from the HeapManager instead code format="cpp" void* operator new(size_t n) { return HeapManager::getInstance.allocate(n); }

void operator delete(void* p) { HeapManager::getInstance.free(p); } code

Exercise 12.5 Test
//Test your HeapManager: Create a number of differently sized objects using regular new and delete, and see// //that you do indeed use the overloaded new and delete-operators, and that they do use the HeapManager.//

The system is tested by initializing 20 Vector2D objects using the overloaded new and delete operators. The info function is used to display the HeapManager status and shows that we are indeed using HeapManager::allocate and HeapManager::free when we call new and delete.

The screenshot below shows the output of the info function before and after any objects are initialized. '1' in the table output mean that the memory block is available. a '0' means that the block is in use. Since we created 20 equally sized objects, the output shows that our HeapManager allocated blocks of 16 bytes when it ran out of 8 byte blocks. After deleting all the objects again, the info function displays the same thing as the top table in the picture, indicating that the delete operator does use the free function.