1

In the simple example C++ code below, I allocate a lot of memory (~4GB) on the heap with new and release it again with delete. In top (or some other memory monitor), I can see that the 4GBs are not returned to the OS until the program is shutdown.

I learned (among others in my related question) that this is normal because the unused heap is returned to the OS in hunks, which does not need to be immediately after the delete.

I would expect that then the already deallocated memory is returned to the OS as soon as it requests more memory (e.g. when I start a second instance of the example program). Unfortunately, this does not seem to be the case: when I run a second instance of my example after the first instance deallocated the memory, the consumption of both instances rises to 8GB. I can repeat this with several instances until all the physical memory of the system is used up and it starts swapping and becoming unresponsive.

The output of top when running 4 instances of the example program (each waiting in std::cin after deleting the data) looks like this:

Mem:     16065M total,    15983M used,       82M free,        0M buffers
Swap:     2053M total,     1323M used,      730M free,      139M cached

Is that really the wanted behavior or am I missing something?

I can observe this on different Linux systems, but not on Mac OS 10.11. On Mac OS the memory is returned to the OS immediately after the delete, what I actually expected to happen.

#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=1000000;

    std::cout<<"Start after input"<<std::endl;
    std::cin >> a;
    // /proc/PROC_ID/status at this point:
    // VmSize:     11468 kB
    // VmLck:          0 kB
    // VmHWM:        960 kB
    // VmRSS:        960 kB
    // VmData:       144 kB
    {
        std::vector<MyObject*> vec(count);
        for(int i=0; i<count; i++)
        {
            vec[i] = new MyObject;
        }

        std::cout<<"Release after input"<<std::endl;
        std::cin >> a;
        // VmSize:   3972420 kB
        // VmLck:          0 kB
        // VmHWM:    3962016 kB
        // VmRSS:    3962016 kB
        // VmData:   3961096 kB

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

    std::cout<<"Shutdown after input"<<std::endl;
    std::cin >> a;
    // VmSize:   3964604 kB
    // VmLck:          0 kB
    // VmHWM:    3962016 kB
    // VmRSS:    3954212 kB
    // VmData:   3953280 kB


    return 0;
}
Community
  • 1
  • 1
schluchc
  • 3,924
  • 2
  • 15
  • 13
  • 4
    *"I can see that the 4GBs are not returned to the OS until the program is shutdown"* - this gets asked all the time. That's the way most OS work. Linux makes an exception for large allocations, which may be released to the OS when they're freed. The memory is available for you to reuse though (via `malloc` or `new`). In general, an OS will let the freed memory pages be swapped out, and they won't affect your overall system's physical memory allocation significantly, so there's no need to worry. – Tony Delroy Jan 25 '16 at 09:53
  • 3
    Your question is very hard to make sense of because you keep using the word 'memory" in contexts in which it's impossible to tell what you're talking about. Are you talking about physical memory (RAM)? Or are you talking about virtual memory (address space)? Or backing store? Or what? It's very hard to tell whether your question is based on just confusions about different types of memory. – David Schwartz Jan 25 '16 at 09:56
  • 3
    Short answer: If you're talking about physical memory, unless it's locked, it's *always* already released to the OS. The OS uses physical memory however it thinks is best unless it's locked. If you're talking about virtual memory (address space), who cares? It's not a scarce resource. As long as it can be re-used for subsequent allocations, there's absolutely no benefit to removing the allocation. It's pure cost since it means more work for later allocations. – David Schwartz Jan 25 '16 at 09:58
  • @TonyD I expected that I don't need to worry, but I can run 4 instances of this program after each other (so that no more than one of them actually needs the 4GBs at the same time) and my system becomes unresponsive. – schluchc Jan 25 '16 at 10:06
  • I'm more than happy to further edit and clarify my question if that helps. But in essence, the title says it all: why can I bring a system to the limits by correctly (as far as I know) allocating and deallocating memory. – schluchc Jan 25 '16 at 10:21
  • Possible duplicate of [What is copy-on-write?](http://stackoverflow.com/questions/628938/what-is-copy-on-write) – Pierre Jan 25 '16 at 10:28
  • @Pierre what? how so? – PeterT Jan 25 '16 at 10:29
  • @DavidSchwartz: I edited the question and added some more output from /proc/PROC_ID/status, which hopefully makes it clear that not only virtual but actually physical memory is still occupied after delete. – schluchc Jan 25 '16 at 10:30
  • @DavidSchwartz I think that swapping memory to disk is orders of magnitude more expensive than returning it to the OS; your allegation "there's absolutely no benefit to removing the allocation. It's pure cost" is imho plain wrong; the opposite is the case. – Peter - Reinstate Monica Jan 25 '16 at 10:38
  • @PeterA.Schneider That section is talking about virtual memory. Maybe it's not clear because you can't put a paragraph break in a comment. Address space whose contents cannot possibly be read will not be written to disk. – David Schwartz Jan 25 '16 at 10:39
  • You can also read the man pages for malloc_trim(3) and mallopt(3). – Marc Glisse Jan 25 '16 at 10:39
  • @DavidSchwartz Well, RAM is part of VM. Not returning VM that happens to be in RAM (which is frequently the case for addresses that have just been written to, as in the OP's example) means they need to be swapped out. – Peter - Reinstate Monica Jan 25 '16 at 10:41
  • @MarcGlisse thanks, yes, that's what I'm doing right at the moment. Nevertheless, I really hope that this can be solved without introducing platform-dependent code. – schluchc Jan 25 '16 at 10:41
  • Look up things like "memory fragmentation". – Peter Jan 25 '16 at 10:57
  • @PeterA.Schneider You're confusing two completely different things. The issue is not that the VM hasn't been returned but that the contents can still be read. For example, if you do a `posix_madvise(MADV_DONTNEED)`, the VM will not be returned to the OS, but its contents will not be written to swap. (This is why it's very important to be precise about what you mean and not use terms like "memory" when it's not clear what exactly you mean.) – David Schwartz Jan 25 '16 at 10:58
  • @DavidSchwartz Did you perhaps want to say "if you do a `posix_madvise(POSIX_MADV_`**WILLNEED**`)`, the VM will **not** be [...] written to swap"?-- I still think what I said is completely valid, unless one takes special measures (like calling `posix_madvise` -- which may or may not be present even in a POSIX system, and may or may not have the desired effect if present). – Peter - Reinstate Monica Jan 25 '16 at 11:15
  • @PeterA.Schneider No, if you do WILLNEED, it will be written to swap. (What choice will it have? You'll need it.) If you do DONTNEED, it won't be written to swap because you've said you don't need the contents, so they don't need to be saved. The issue is not the virtual memory but the value of the contents of that memory. They're two different things. – David Schwartz Jan 25 '16 at 11:16
  • @DavidSchwartz While I'm not familiar with the function the man pages agree that `POSIX_MADV_DONTNEED` "Specifies that the application expects that it will not access the specified range in the **near** future". Does that mean "not needed at all any longer, go and dump it"? Otoh, the indication which `POSIX_MADV_WILLNEED ` gives, namely "that the application expects to access the specified range in the near future", seems to make it a wise choice to keep it in RAM. Of course, "near future" is an impricise term, but why would an OS write data to disk which is labeled "soon to be accessed"? – Peter - Reinstate Monica Jan 25 '16 at 11:22
  • @PeterA.Schneider An OS would write data to disk which is labelled "soon to be accessed" because it has no choice. If it needs the physical memory for some other purpose, it must save the contents. As for POSIX_MADV_DONTNEED, we're talking about anonymous allocations here, not backed by a file. In that case, yes, it means the contents need not be saved by the OS and typical OSes will immediately return the mapping to a state where a blank page will be mapped in the next time it's accessed. But the point is -- whether VM is allocated is not the problem, it's the contents. – David Schwartz Jan 25 '16 at 11:25
  • @DavidSchwartz The man page says clearly "The posix_madvise() function shall have **no effect on the semantics** of access to memory in the specified range". You can't be any more plain than that, and it flatly contradicts what you say. Did you perhaps mean NetBSD's `MADV_FREE` which is not part of the POSIX spec, presumably for that very reason (changes semantics)? – Peter - Reinstate Monica Jan 25 '16 at 11:29
  • @PeterA.Schneider Sorry, meant `madvise`, not `posix_madvise` and `MADV_DONTNEED`, not `POSIX_MADV_DONTNEED`. But it's just an example. The allocation of VM is irrelevant, it's whether or not the contents have to be saved that's the issue. VM is effectively free. – David Schwartz Jan 25 '16 at 11:31
  • @DavidSchwartz I see :-) took you a while. And I still think you meant BSD's `MADV_FREE`, because the NetBSD man page says about `MADV_WILLNEED` "if they are in memory, decrease the likelihood of them being freed", which make a lot of sense, and the opposite about `MADV_DONTNEED`. – Peter - Reinstate Monica Jan 25 '16 at 11:35
  • @PeterA.Schneider Well, I was trying to show the importance of being precise. I accidentally succeeded beyond what I expected. ;) – David Schwartz Jan 25 '16 at 11:39
  • @schluchc: bottom line is that it's not normally worth the overheads of tracking when entire virtual memory pages become free and handing them back to the OS, only to need to re-request them later. Instead, the OS exposes an [`sbrk()`](http://linux.die.net/man/2/sbrk) function that moves a "high water mark" for memory consumption. If you want to release the memory, I suggest you let your previously memory-hungry process terminate and have a new process do whatever's still needed, or as David's answer suggests allocate large enough pages that the OS does release them for you. – Tony Delroy Jan 25 '16 at 11:42

1 Answers1

2

Your platform's memory allocator doesn't handle this particular case the way you would like it to. Most likely, it only returns sufficiently large allocations to the operating system and these allocations are too small. If you have unusual requirements, you should select an allocator known to meet those requirements. Having a very large number of very small allocations that are freed at the same time is such an unusual application.

It may be as simple as a wrapper allocator that allocates blocks much larger than requested and splits them up. This should make the blocks large enough for your platform's allocator to map them one-to-one to address space requested from the operating system.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • So by "select an allocator", do you have a suggestion (beyond writing your own)? I'm also puzzled because glibc's `malloc()` seems to have an M_TRIM_THRESHOLD of 128k which should have literally been triggered a million times by the OP's use case, and rightly so. I can only conclude that the standard allocator coming with g++ doesn't use malloc as a back end. Otherwise I'd have suggested to fiddle with `M_TRIM_THRESHOLD` and possibly `M_MMAP_THRESHOLD` which would make any freed memory available to the OS if set to a smaller value. But it may have no effect for the C++ runtime. – Peter - Reinstate Monica Jan 25 '16 at 12:11
  • @PeterA.Schneider `M_TRIM_THRESHOLD` is rarely useful, especially in C++ programs which tend to involve a lot of small ad-hoc allocations - you just need a single string or something allocated after a huge allocation to ensure the latter won't be releasable until the former's deallocated. `M_MMAP_THRESHOLD` is the parameter that holds some promise: you need to get a memory pool / allocator (boost has one that might suit) and use it to dish out small portions from the large allocation. Don't mix in any objects you need to live after the program's released most memory. – Tony Delroy Jan 25 '16 at 12:31