0

There is something I don't understand and I would highly appreciate some clarification. I know there is a lot around about std::containers and memory not freed, but I still don't understand one particular fact.

Below is a minimal program that represents a problem I have in my productive system. In comments there is the memory consumption read from /proc/PROC_NUM/status while waiting for std::cin on Ubuntu. Questions are also in the comments.

#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <vector>

class MyObject
{
public:
    MyObject(int r=1000)
    : array(new float[r])
    {
        for (int i = 0; i<r;i++)
        {
            array[i] = random();
        }
    }

    ~MyObject()
    {
        delete[] array;
    }

public:
    float* array;
};


int main(int argc,char*argv[])
{
    char a;
    const int count=100;

    std::cout<<"Start after input"<<std::endl;
    std::cin >> a;
    // VmSize:     12704 kB
    {
        std::vector<MyObject*> vec;
        for(int i=0; i<count; i++)
        {
            vec.push_back(new MyObject);
        }

        std::cout<<"Release after input"<<std::endl;
        std::cin >> a;
        // VmSize:     13100 kB, alright, MyObjects fill around 400kB (what I expected)

        for (int i=0; i<count; i++)
        {
            delete vec[i];
            vec[i]=NULL;
        }

        std::cout<<"Run out of scope of vector after input"<<std::endl;
        std::cin >> a;
        // VmSize:     13096 kB, Why are the 400k not freed yet?
    }

    std::cout<<"Shutdown after input"<<std::endl;
    std::cin >> a;
    // VmSize:     12704 kB, Why are now around 400k freed? The only thing that is freed here is the vector of pointers.

    return 0;
}

If I use an array of MyObjects instead, memory is freed immediately after I delete it:

#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <vector>

class MyObject
{
public:
    MyObject(int r=1000)
    : array(new float[r])
    {
        for (int i = 0; i<r;i++)
        {
            array[i] = random();
        }
    }

    ~MyObject()
    {
        delete[] array;
    }

public:
    float* array;
};


int main(int argc,char*argv[])
{
    char a;
    const int count=100;

    std::cout<<"Start after input"<<std::endl;
    std::cin >> a;
    // VmSize:     12700 kB
    {
        MyObject* vec(new MyObject[count]);

        std::cout<<"Release after input"<<std::endl;
        std::cin >> a;
        // VmSize:     13096 kB, alright, around 400k again
        delete[] vec;

        std::cout<<"Run out of scope of vector after input"<<std::endl;
        std::cin >> a;
        // VmSize:     12700 kB, 400k freed again, perfect.
    }

    std::cout<<"Shutdown after input"<<std::endl;
    std::cin >> a;
    // VmSize:     12700 kB, nothing changed, as expected
    return 0;
}

I read answers telling that I can't trust the OS' memory numbers (currently I used the output of /prop/PROC_NO/status on Linux). What instead could I use to monitor the memory consumption? I tried the same on Mac in XCode Instruments and there, I don't even have that problem. Meaning memory consumption in the first case is equal to the second case.

On Ubuntu, I tried different versions of gcc and clang and they all showed the same behavior.

In my productive system, there is a std::map instead of a std::vector, but the problem is the same.

schluchc
  • 3,924
  • 2
  • 15
  • 13
  • You can use custom allocators with trace/debug support to get a more accurate picture of memory consumption. But it's best to just design for correctness. Use smart pointers and containers, avoid plain `new`. – Cheers and hth. - Alf Jan 22 '16 at 13:16

3 Answers3

3

The VMSize has little to do with the entering and leaving of scopes in a scope language (are ware that there are programming environments that don't have scope?).

The VMSize reflects which memory the operating system actually has to use to satisfy a program's memory requirement. For example if you allocate a large block of memory using malloc, new[] or anonymous mmap the address space is merely reserved, but not occupied, thereby not showing up in the VMSize.

Furthermore most runtime libraries allocate memory in large hunks and object allocations are then slices from these large hunks; after freeing an object only the space in the hunk is marked as free and may get recycled for the next allocation. A typical cue to free such a large hunk of memory is, if all objects allocated from it are freed and no further object resides in it. So your std::vector and the objects you manually allocated are likely allocated from the same hunk and the std::vector instance sitting around prevents it from being returned to the system. But this is all just conjecture! The details depend on the C++ runtime library used.

datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • I agree with your hunk theory, the only thing that confuses me here is: Why can I track the consumption in my second example? There the VMSize somehow reflects the consumption quite well. What also doesn't quite fit to that theory, why is the change once the vector is destroyed (in my example by leaving the scope) reflected in VMSize? – schluchc Jan 22 '16 at 14:10
  • @schluchc: Following the conjecture, In your second example only your manually managed array of `MyObjects` occupy the hunk and after deletion the hunk is free. That's in contrast to your first example where the supposed hunk is still occupied by the `std::vector` instance (and all the now-dead pointers it holds) until that one goes out of scope. – datenwolf Jan 22 '16 at 15:33
  • I guess you are somehow right, but pushing the example to the limits, really makes me doubt it: Take my first code snippet and change the for loop count to 1'000'000, then the program takes roughly 4GB of RAM which is not returned before the program is finished. I cannot believe that a hunk of 4GB is not freed because possibly 100Bytes of it are still in use. – schluchc Jan 22 '16 at 16:16
0

In your first example your created vector on stack in scope. As you may know C++ has RAII http://en.cppreference.com/w/cpp/language/raii to automatically resource management, and when you leave the scope memory reserved to vector will be deallocated. This is why you see VmSize: 12704 again at the end of your example!

If you create vector not on stack the behavior of your first example will be as your expected.

#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <vector>
#include "stdlib.h"
#include "stdio.h"
#include "string.h"


int parseLine(char* line){
    int i = strlen(line);
    while (*line < '0' || *line > '9') line++;
    line[i-3] = '\0';
    i = atoi(line);
    return i;
}


int getValue(){ //Note: this value is in KB!
    FILE* file = fopen("/proc/self/status", "r");
    int result = -1;
    char line[128];


    while (fgets(line, 128, file) != NULL){
        if (strncmp(line, "VmSize:", 7) == 0){
            result = parseLine(line);
            break;
        }
    }
    fclose(file);
    return result;
}

class MyObject
{
public:
    MyObject(int r=1000)
    : array(new float[r])
    {
        for (int i = 0; i<r;i++)
        {
            array[i] = random();
        }
    }

    ~MyObject()
    {
        delete[] array;
    }

public:
    float* array;
};


int main(int argc,char*argv[])
{

    const int count=100;
    // VmSize:     12704 kB
    std::cout << "VmSize: " << getValue() << std::endl;
    {
        std::vector<MyObject*> *vec = new std::vector<MyObject*>();
        for(int i=0; i<count; i++)
        {
            vec->push_back(new MyObject);
        }

        // VmSize:     13100 kB, alright, MyObjects fill around 400kB (what I expected)
        std::cout << "VmSize: " << getValue() << std::endl;

        for (int i=0; i<count; i++)
        {
            delete (*vec)[i];
            (*vec)[i]=NULL;
        }

//Now we deallocating memory! In case of stack it will be done automatically when we left the scope.
        delete vec;
        std::cout << "VmSize: " << getValue() << std::endl;
        // VmSize:     13096 kB, Why are the 400k not freed yet?
    }

    std::cout << "VmSize: " << getValue() << std::endl;
    // VmSize:     12704 kB, Why are now around 400k freed? The only thing that is freed here is the vector of pointers.

    return 0;
}
Denis Zaikin
  • 569
  • 4
  • 10
  • I know that I didn't delete the vector elements, but they are only pointers, which definitely doesn't make up the 400k. The 400k originate from the 100*1000*4 bytes the floats in the MyObject consume. So my question is, why are they not given back yet. I never gave ownership of them to the vector. Nevertheless, I tried adding a clear, but it didn't change anything. – schluchc Jan 22 '16 at 14:05
  • @schluchc i'm wrong about vector element, i've changed my answer. – Denis Zaikin Jan 26 '16 at 08:00
0

Your expectation is correct! This happens because of GCC's STL allocation strategy. GCC uses heuristics. For example you may need to re-insert object. So until the whole vector goes away, your call to destructor of MyObject is null statement internally! Full explanation can be found here: https://gcc.gnu.org/onlinedocs/libstdc++/manual/memory.html.

As per the explanation from the above link: to switch off the STL optimization, set GLIBCXX_FORCE_NEW. I tried like this:

export GLIBCXX_FORCE_NEW=1
./a.out

Now the memory is getting freed immediately!

SarvanZ
  • 90
  • 4