DEV Community

pratheek87
pratheek87

Posted on

Memory management techniques to reduce memory consumption and improve performance.

Every memory allocation comes with a overhead of book keeping data. Also, every memory request results in a call to the operating system. Imagine a scenario where you would need to create millions of tiny objects over the entire time of the application usage. In environments where memory is a serious constraint it could lead to application failures because of the memory consumed by the book keeping data.

In this post, I would like to share a common technique used to handle memory management to address the above concerns.

Key concepts:

  1. Creating a memory pool with the required size. Future allocations would be made from this pool rather than directly allocating from the OS. This would also avoid memory consumed by book-keeping.
  2. Overload the new operator to handle memory allocation requests using this pool.
  3. Make the memory allocator class to be used across by multiple classes rather than individual classes.

Code Snippet:

  1. Write a class that creates a memory pool of required size. The key function here is the createpool method that calls malloc to allocate memory of required size and creates a list of FreeList pointers.
class memallocator
{
public:
    memallocator(size_t reserve) : mReserve(reserve) {}

private:
    size_t mReserve;
    FreeList* mBegin = nullptr;
    stack<FreeList*> freeptrs;
public:

    void* allocate(size_t s)
    {
        if (mBegin == nullptr)
        {
            if (!freeptrs.empty())
            {
                void* ptr = freeptrs.top();
                freeptrs.pop();
                return ptr;
            }
            else
                createPool(s);
        }
        FreeList* pFreeList = mBegin;
        mBegin = mBegin->next;

        return pFreeList;
    }

    void deallocate(void* ptr, size_t s)
    {
        freeptrs.push(reinterpret_cast<FreeList*>(ptr));
    }


    void createPool(size_t s)
    {
        int total_size = s * mReserve;
        FreeList* pMem = reinterpret_cast<FreeList*>(malloc(total_size));
        FreeList* temp = pMem;

        //Create the list of pointers.
        for (auto i = 0; i < mReserve-1; ++i)
        {
            temp->next = reinterpret_cast<FreeList*>(reinterpret_cast<char*>(temp) + s);
            temp = temp->next;
        }

        temp->next = nullptr;
        mBegin = pMem;
    }
};

Enter fullscreen mode Exit fullscreen mode

FreeList is defined as a struct below that would be used to keep track of the next pointer.

struct FreeList
{
    FreeList* next;
};
Enter fullscreen mode Exit fullscreen mode
  1. In the custom class overload the new and delete operators to make use of the new allocator we have created.
class MyCustomClass
{

    string mptrData;
    double d;

public:
    static memallocator allocator;
    MyCustomClass(const char* data, int s, double val) : d(val) {
        mptrData = data;
    }

    static void* operator new(size_t s)
    {
        return allocator.allocate(s);
    }

    static void operator delete(void* ptr, size_t s)
    {
        allocator.deallocate(ptr, s);
    }

    string sendData()
    {
        return mptrData + " " + to_string(d);
    }
};
Enter fullscreen mode Exit fullscreen mode

The below code is needed to initialize the allocator object. We are reserving space for about 96000 objects.

memallocator MyCustomClass::allocator{ 96000 }; // Reserve space for 24 objects.
Enter fullscreen mode Exit fullscreen mode

** Note **: 96000 was used for testing purposes only.

  1. Let us write the main function to test
int main()
{
    const char* data = "This is test object : ";
    long long int begin = GetTickCount64();

    for (auto i = 0; i < 240000; ++i)
    {
        auto* mc = new MyCustomClass(data, strlen(data), i);
    }

    long long int end = GetTickCount64();
    double elapsed = (end - begin);

    cout << "Time measured: %.9f miliseconds.\n" <<  elapsed << endl;
Enter fullscreen mode Exit fullscreen mode
Key takeaways:
  1. When tested with the memory allocator we have written along with the default we could see that our memory allocator was efficient slightly with time (since my CPU is quite powerful I guess :) ) but very efficient in terms of the memory consumed. For the above sample it was around 10K less taken by our memory allocator which is quite a decent gain.
  2. This is not industry standard approach rather a simple technique that needs to be customized for specific requirements.

Please reach out to me for any further questions.

Latest comments (2)

Collapse
 
linehammer profile image
linehammer

A class without an object requires no space allocated to it. The memory allocation takes place only when you create objects because the objects is what implements the contents of the class. So to an object of an empty class, 1 byte is allocated by compiler, for it's unique address identification. If a class have multiple objects they can have different unique memory location. When you create new class, it will call an allocation function . This function can have different implementations. This depends on the object oriented programming language, the language version, libraries, and configuration.

net-informations.com/faq/oops/oops...

Collapse
 
pratheek87 profile image
pratheek87

Thanks @linehammer for your comment. The above post focuses more on how to optimize performance when you need to allocate memory for lots of objects over the entire time of the application usage.