5

We are having some memory issues when std::vector is a field of a class. We fill this vector with a lot of data which, at a certain point of the program needs to be released. However, even though the vector capacity is zero, the memory is not released or completely released.

Here you have a simplified version of our program. As you can see, class Foo has only one field: a std::vector<int>. If we create a std::vector<Foo> and fill it with Foo objects, when we empty the vector inside each object, the memory is not completely released.

We have measured the memory usage using the activity monitor and you can see, next to each log line, the amount of Bytes used in each stage. Moreover, we have added another version where we don't use class Foo objects and, in that case, memory is released perfectly.

#include <iostream>
#include <vector>

class Foo {

public:
    std::vector<int> container;
};

int main() {
    int n1 = 1000;
    int n2 = 100000;
    {
        std::vector<Foo> foos;

        std::cerr << "starting" << std::endl; // 160 KiB
        std::cin.get();

        for (int i = 0; i < n1; i++){
            Foo foo;
            foo.container.assign(n2, 666);
            foos.push_back(foo);
        }

        std::cerr << "foos filled" << std::endl; // 382.1 MiB
        std::cin.get();

        for (unsigned int i = 0; i < foos.size(); i++){
            std::vector<int>().swap(foos[i].container);
        }

        std::cerr << "foos emptied" << std::endl; // 195.7 MiB
        std::cin.get();
    }
    std::cerr << "foos destroyed?" << std::endl; // 296 KiB
    std::cin.get();

    {
        std::vector<std::vector<int> > foos;

        std::cerr << "starting" << std::endl; // 296 KiB
        std::cin.get();

        {
            std::vector<int> aux;
            aux.assign(n2, 666);
            foos.assign(n1, aux);
        }

        std::cerr << "foos filled" << std::endl; // 382.1 MiB
        std::cin.get();

        for (unsigned int i = 0; i < foos.size(); ++i) {
            std::vector<int>().swap(foos[i]);
        }

        std::cerr << "foos emptied" << std::endl; // 708 KiB
        std::cin.get();
    }

    std::cerr << "foos destroyed?" << std::endl; // 708 KiB
    std::cin.get();


    return 0;
}

If it helps, we're using g++ 4.8.4 under Ubuntu 14.04 64-bit. The specific memory occupancy varies depending on whether we use C++11 or C++98, but the same phenomenon occurs in both cases.

Any ideas as to what's happening and how to recover that memory, forcibly if need be?

EDIT: Note that memory is mostly returned indeed when we destroy all objects of the Foo class, but in our real-world problem we still need the rest of the contents of the Foo-analogue class.

rafapages
  • 53
  • 6
  • 6
    How did you measure? Usually any memory that was aquired by the process from the OS, isn't _"turned back"_ if the memory is freed internally. – πάντα ῥεῖ Jul 07 '16 at 12:44
  • 1
    It's part of life I'm afraid. But does it actually cause a problem? Modern operating systems and C++ runtimes are good at freeing up memory when they really need to. – Bathsheba Jul 07 '16 at 12:44
  • Freeing memory in an application need not release all the memory back to the OS since the allocations at the OS level are probably larger chunks. – drescherjm Jul 07 '16 at 12:45
  • 3
    Memory is released to user space memory allocator from runtime C++/C libraries. In general it doesn't mean that user space allocator will return this memory back to OS. User space allocator allocates memory from kernel by blocks. This blocks further are sliced on your requests through new/malloc. When you free/delete this sliced blocks, they are returned to user space allocator, not the kernel. And when user space allocator will be able to return allocate block of memory back to kernel known only user space allocator. –  Jul 07 '16 at 12:46
  • @user1641854 why this is just a comment and not an answer? – 463035818_is_not_an_ai Jul 07 '16 at 12:47
  • @tobi303, thank you for reassurance –  Jul 07 '16 at 12:48
  • @user1641854 wasn't meant as reassurance, but only if you post it as answer others will have the chance to up/downvote (and possibly reassure ;) – 463035818_is_not_an_ai Jul 07 '16 at 12:49

2 Answers2

10

Memory is released to user space memory allocator from runtime C++/C libraries. In general it doesn't mean that user space allocator will return this memory back to OS. User space allocator allocates memory from kernel by blocks. This blocks further are sliced on your requests through new/malloc. When you free/delete this sliced blocks, they are returned to user space allocator, not the kernel. And when user space allocator will be able to return allocated block of memory back to kernel is known only by user space allocator.

  • That may be the case, but the problem is that the memory that the user space allocator has not returned seems to be tied to objects created under the class Foo. In our real-world problem, we have a class that uses several GB and should return them so that other classes can reuse that memory but the other classes end up receiving new chunks of memory and we end up hitting the swap memory. I've updated the question to better reflect the problem. – rafapages Jul 07 '16 at 15:30
  • @rafapages do you use any kind of reference counting ? –  Jul 07 '16 at 15:56
  • 1
    @rafapages in any case - you can profile your heap consumption e.g. use valgrind tool called massif. You will be able to understand what exactly object doesn't free memory. Or, if valgrind massif is not a case, you can write your own allocator, that tracks all allocations/deallacations. It is not very hard. –  Jul 07 '16 at 15:59
  • the thing is that I do know exactly where the memory is lost... The problem is to free it. – rafapages Jul 11 '16 at 08:59
  • if you want to give particular memory to kernel the only option you have is to write own custom alocator, that will do mmap/munmap as @Mike Vine suggests. –  Jul 11 '16 at 13:02
3

@user1641854's answer is correct to why this happens. This answer is about fixing it.

There's a relatively easy way to fix your issue. You can give your vector an allocator which internally directly asks the operating system for memory and directly free's it back to the operating system when free'd. This is generally undesirable as direct from OS allocation is generally slower to allocate/free than a well designed usermode heap and you'll waste some memory at the end of pages. Also, without some effort, it would not be cross platform.

Having said that your case seems one where this would be a reasonable thing to attempt.

See here for how to define your own allocator.

Then use ::VirtualAlloc/::VirtualFree on windows or mmap/munmap on linux for the underlying allocation/free functions.

Community
  • 1
  • 1
Mike Vine
  • 9,468
  • 25
  • 44