2

As per the standard, when the destructor of a std::unordered_map is called (for example when it leaves the scope it is created in), one expects the memory it allocates to be freed. However a simple experiment I have ran on multiple machines seems to conflict with this? Consider the following program:

    #include <chrono>
    #include <iostream>
    #include <map>
    #include <unordered_map>
    #include <memory>
    #include <thread>
    #include <vector>

    void CreateMap() {
        std::unordered_map<int, int> testMap;

        for (int i=0; i < 10000000; i++) {
            testMap[i] = 5;
        }

        std::cout << "finished building map" << std::endl;

        std::this_thread::sleep_for (std::chrono::seconds(15));

        std::cout << "about to exit from CreateMap()" << std::endl;
    }

    int main()
    {
        CreateMap();

        CreateMap(); 

        CreateMap();
    while (true) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    return 0;
}

On my machine, 10% RAM is consumed when the map is finished building but we sleep at the end in CreateMap(). However after exiting the RAM only goes down to 8% (one can play with various sizes of the map to show that the map itself is responsible for more than 2%) so one would expect that CreateMap is leaking memory? However 3 calls to CreateMap() or just one call seem to not make a difference (so the memory is recycled to the program but not the RAM?).

Could this be something strange about OS memory management that I don't understand ,i.e., it is possible for a program to free memory for its own future use (future allocations) but not to the OS (i.e., for memory allocations in other programs)?

  • 1
    There is a heap management which comes with the standard (or runtime) libraries of the compiler. Whenever it goes out of memory, it tries to acquire more from the OS. When memory is freed in C++ it's not necessarily returned to OS but might usually be kept in a "free" list to be available for later requests. Hence, you might not see this free memory when using OS memory inspection tools. – Scheff's Cat Feb 07 '20 at 11:10
  • The OS doesn't have to release the pages allocated for your process immediately, so they can still be counted when checking memory (depending on *how* you check memory). – Some programmer dude Feb 07 '20 at 11:11
  • FYI: [CS: How is heap memory allocated to a process?](https://cs.stackexchange.com/q/26463) – Scheff's Cat Feb 07 '20 at 11:14
  • 1
    A very similar question was asked this morning. you may be interested to read [this answer](https://stackoverflow.com/a/60110159/11455384). – Fareanor Feb 07 '20 at 11:14
  • Another related question here: https://stackoverflow.com/questions/32683699/force-memory-release-to-the-os – TuanDT Feb 07 '20 at 11:31
  • @Scheff Thanks for that. So would this mean that if I ran my program say 20 times i.e., run one instance, let it stabilize (leave CreateMap()) then run another etc., I would not run out of RAM? i.e., could one program "steal" the "free" RAM of another? – Mathemagician Feb 07 '20 at 11:31
  • _if I ran my program say 20 times_ To run a program, the OS has to create a process which is assigned OS resources (upon request). Once, the process exits it gives back all its acquired resources to the OS (with few special exceptions, on the common modern OSes like Windows, Linux, Mac OSX). If you start the same program again, that's a new process. – Scheff's Cat Feb 07 '20 at 11:34
  • @Scheff I do not mean that I run it, then exit and run another etc. (notice the infinite loop that sleeps forever at the end) but rather that all instances run simultaneously eventually (they start at different times so that the CreateMap() runtimes don't overlap). So if one uses 8% RAM, would 20 need 160% or would they steal it from each other since it is "free"? – Mathemagician Feb 07 '20 at 11:40
  • One process cannot steal memory assigned to another. (Not from the same program nor from another.) Modern OSes (like the mentioned above) must prevent this. (Ever heard about "Access Violation"?) ;-) Aside of this, there is the concept of virtual memory which is mapped to physical memory pages whereby memory pages may be swapped out (e.g. to disk) if the OS gets short on free physical memory. – Scheff's Cat Feb 07 '20 at 11:43
  • Does this answer your question? [Force memory release to the OS](https://stackoverflow.com/questions/32683699/force-memory-release-to-the-os) – ShadowRanger Feb 07 '20 at 11:43
  • @ShadowRanger Thanks for the link. The answer says "Rest assured, if you have freed this memory properly, and the OS really needed it back, it would be reassigned." but the comment reply seems to disagree, as well as the comment of Scheff above ("...cannot steal memory assigned to the other..."). So I am not sure, maybe I am misinterpreting something. – Mathemagician Feb 07 '20 at 12:14
  • @Mathemagician: They're speaking loosely; if even a single allocation remains active in a given block of the heap, that block *can't* be *officially* returned to the OS; it remains the property of the program (future allocations will use it before asking the OS for more). But on typical systems (with swap/page file), any page that's unused for any length of time will be prioritized for paging out. So in terms of virtual memory (per-process resource), the memory is never returned, but it's unused and will be readily paged out to give the physical RAM (global resource) to other processes. – ShadowRanger Feb 07 '20 at 17:51
  • 1
    @Mathemagician: Depending on the OS and the allocator, it can even assist this process by providing hints to the OS, e.g. on Linux it might `madvise` `mmap`ed pages (typically a fraction of the block size) when there are no remaining allocations as `MADV_DONTNEED`, which tells the OS it can drop the data; if the pages are accessed later, the OS just provides them on demand filled with zeroes. So technically the memory is still "allocated" (virtual address space is still in use, the memory can be accessed if needed), but no physical RAM or swap file is being used. – ShadowRanger Feb 07 '20 at 18:04

2 Answers2

2

You're testing wrongly. The code doesn't leak, but released memory isn't necessarily made available to other processes (what you're measuring) - it will likely remain claimed for future allocation by the same process.

For example, after removing the infinite loop, and reducing the limit on i to fit my test sandbox, I ran the code under Valgrind:

valgrind --leak-check=full ./60112215   
==3396096== Memcheck, a memory error detector
==3396096== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3396096== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==3396096== Command: ./60112215
==3396096== 
finished building map
about to exit from CreateMap()
finished building map
about to exit from CreateMap()
finished building map
about to exit from CreateMap()
==3396096== 
==3396096== HEAP SUMMARY:
==3396096==     in use at exit: 0 bytes in 0 blocks
==3396096==   total heap usage: 300,044 allocs, 300,044 frees, 13,053,168 bytes allocated
==3396096== 
==3396096== All heap blocks were freed -- no leaks are possible
==3396096== 
==3396096== For lists of detected and suppressed errors, rerun with: -s
==3396096== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

If you want to really demonstrate this for yourself, you can call CreateMap() many more times and see that the process's memory usage doesn't increase:

int main()
{
    for (auto i = 0;  i < 100;  ++i) {
        CreateMap();
    }
}

It's clearly re-using the memory that was freed in the previous iteration.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
0

Assure yourself that std::unordered_map doesn't leak. You cannot use a system monitor to check if your program is leaking, period. If anything, you should look at physical memory usage and not total or virtual. Even physical memory is not accurate of the RAM usage since pages can be swapped to disk (pagefile). Even then, optimizations at eg. the OS or c++ language level may be reusing heap memory (as mentioned in comment) for optimizations. There are too many factors involved and it's just too much of a gross value to be usefull to something as delicate as detecting leaked memory. A simpel (and often effective) approach to look for leaks is looking at the heap at program termination, eg. using _CrtSetDbgFlag or similar tools. Needless to say, avoiding hand-rolled memory management and using ownership semantics goes a long way in avoiding leaking code.

darune
  • 10,480
  • 2
  • 24
  • 62