On 11/22/2016 3:06 PM, Christopher J. Pisz wrote:
> So, I got asked on an interview today if I am have ever done a custom
> memory manager and if I am familiar with placement new.
>
> Nope. I think I am accustomed to more high level programming then what
> they are looking for. None the less, I'd like to learn about the concept.
>
SNIP
So, I've Googled and read a few articles tonight. I implemented a test
(found below) and holy crap. It makes a difference by a factor of 100 on
my machine with the current settings in Release. Going from 12 seconds
to .12 seconds is pretty darn significant. So, there is something to be
said for this.
The test I implemented was following an article from IBM. I don't
understand a couple of things that they did:
1) They allocate 1 element at a time, rather than numElements * sizeof
the object to be stored. Why?
Also, the trick they are using in allocating the size of the Data object
and treating it like a Node pointer seems to be essential to their
design. How can we change the source to allocate numElements * sizeof
the object instead, if we wanted to?
2) Given that they are allocating one element at a time, just up front,
why am I seeing a performance increase as drastic as I am?
3) I don't seem to get any performance increase if I change the pool
size from 32 to 1024. Why?
Here is the amalgamated source code:
// Common Library
// #include "PerformanceTimer.h"
// Standard Includes
#include <iostream>
//------------------------------------------------------------------------------
const size_t g_numElements(10000);
const size_t g_iterations(5000);
const size_t g_poolSize(32);
//------------------------------------------------------------------------------
// Data class with no memory management
class NoMemoryManagement
{
public:
NoMemoryManagement(double realPart, double SpecificToSimpleMMPart);
private:
double m_realPart;
double m_complexPart;
};
//------------------------------------------------------------------------------
// Data class that overwides operator new and delete to use simple
memory managemer
class SpecificToSimpleMM
{
public:
SpecificToSimpleMM(double realPart, double SpecificToSimpleMMPart);
void * operator new(size_t size);
void operator delete(void * pointerToDelete);
private:
double m_realPart;
double m_complexPart;
};
//------------------------------------------------------------------------------
class IMemoryManager
{
public:
virtual void * allocate(size_t) = 0;
virtual void free(void *) = 0;
};
//------------------------------------------------------------------------------
// Memory Manager that allocates space for multiple objects, of a
specific type, at a time
//
// Customized for objects of type SpecificToSimpleMM and works only in
single-threaded environments.
// Keeps a pool of SpecificToSimpleMM objects available and has future
allocations occur from this pool.
class SimpleMemoryManager : public IMemoryManager
{
// Node in the memory pool
struct FreeStoreNode
{
FreeStoreNode * m_next;
};
void expandPoolSize();
void cleanUp();
// The memory pool
FreeStoreNode * m_freeStoreHead;
public:
SimpleMemoryManager();
virtual ~SimpleMemoryManager();
virtual void * allocate(size_t);
virtual void free(void *);
};
//------------------------------------------------------------------------------
NoMemoryManagement::NoMemoryManagement(double realPart, double complexPart)
:
m_realPart(realPart)
, m_complexPart(complexPart)
{
}
//------------------------------------------------------------------------------
SimpleMemoryManager g_memoryManager; // Global yuck! Just following the
online example
//------------------------------------------------------------------------------
SpecificToSimpleMM::SpecificToSimpleMM(double realPart, double complexPart)
:
m_realPart(realPart)
, m_complexPart(complexPart)
{
}
//------------------------------------------------------------------------------
void * SpecificToSimpleMM::operator new(size_t size)
{
return g_memoryManager.allocate(size);
}
//------------------------------------------------------------------------------
void SpecificToSimpleMM::operator delete(void * pointerToDelete)
{
g_memoryManager.free(pointerToDelete);
}
//------------------------------------------------------------------------------
SimpleMemoryManager::SimpleMemoryManager()
{
expandPoolSize();
}
//------------------------------------------------------------------------------
SimpleMemoryManager::~SimpleMemoryManager()
{
cleanUp();
}
//------------------------------------------------------------------------------
void SimpleMemoryManager::expandPoolSize()
{
// The trick to the design of this memory manager is that each
element that is
// allocated is the larger of a FreeStoreNode pointer or the size
of the object being stored.
// In this case, we are storing SpecificToSimpleMM objects, which
will be larger than a pointer.
// So, while we treat elements as FreeStoreNode pointers, the void
pointer returned by operator new
// points to SpecificToSimpleMM objects.
size_t size = (sizeof(SpecificToSimpleMM) > sizeof(FreeStoreNode
*)) ? sizeof(SpecificToSimpleMM) : sizeof(FreeStoreNode *);
FreeStoreNode * head = reinterpret_cast<FreeStoreNode *>(new
char[size]);
m_freeStoreHead = head;
for (int i = 0; i < g_poolSize; i++)
{
head->m_next = reinterpret_cast<FreeStoreNode *>(new char[size]);
head = head->m_next;
}
head->m_next = 0;
}
//------------------------------------------------------------------------------
void SimpleMemoryManager::cleanUp()
{
// Only cleans up memory from the blocks we allocated that did not
get used yet
// Otherwise, we have to rely on the user deleting any object he
newed, as normal
FreeStoreNode * nextPtr = m_freeStoreHead;
for (; nextPtr; nextPtr = m_freeStoreHead)
{
m_freeStoreHead = m_freeStoreHead->m_next;
delete[] nextPtr; // remember this was a char array
}
}
//------------------------------------------------------------------------------
void * SimpleMemoryManager::allocate(size_t size)
{
if (!m_freeStoreHead)
{
expandPoolSize();
}
FreeStoreNode * head = m_freeStoreHead;
m_freeStoreHead = head->m_next;
return head;
}
//------------------------------------------------------------------------------
void SimpleMemoryManager::free(void * deleted)
{
FreeStoreNode * head = static_cast<FreeStoreNode *> (deleted);
head->m_next = m_freeStoreHead;
m_freeStoreHead = head;
}
//------------------------------------------------------------------------------
void RunNoMemoryManagement()
{
/*
// Start timer
Common::PerformanceTimer timer;
timer.Start();
*/
// Do the work
NoMemoryManagement * array[g_numElements];
for (int i = 0; i < g_iterations; i++)
{
for (int j = 0; j < g_numElements; j++)
{
array[j] = new NoMemoryManagement(i, j);
}
for (int j = 0; j < g_numElements; j++)
{
delete array[j];
}
}
/*
// Stop the timer
double secondsElapsed = timer.Stop();
std::cout << "Test with no memory management took " <<
secondsElapsed << " seconds.\n";
*/
}
//------------------------------------------------------------------------------
void RunSimpleMemoryManagement()
{
/*
// Start timer
Common::PerformanceTimer timer;
timer.Start();
*/
// Do the work
SpecificToSimpleMM * array[1000];
for (int i = 0; i < 5000; i++)
{
for (int j = 0; j < 1000; j++)
{
array[j] = new SpecificToSimpleMM(i, j);
}
for (int j = 0; j < 1000; j++)
{
delete array[j];
}
}
/*
// Stop the timer
double secondsElapsed = timer.Stop();
std::cout << "Test with simple memory management took " <<
secondsElapsed << " seconds.\n";
*/
}
//------------------------------------------------------------------------------
int main(int argc, char * argv[])
{
RunNoMemoryManagement();
RunSimpleMemoryManagement();
return 0;
}