Thread: Unordered map behavior on clear and reserve

  1. #1
    ¡Amo fútbol!
    Join Date
    Dec 2001
    Posts
    2,138

    Unordered map behavior on clear and reserve

    In one of my projects, I'm using an unordered_map where I repeatedly clear and refill it. Each time, the number of points is roughly similar (but not identical), so preventing reallocation is beneficial.

    With std::vector, calling clear or resize with a smaller size does not release existing memory held by the vector.

    Code:
    int size1 = 100;
    int size2 = 50;
    std::vector<int> foo;
    foo.resize(size1);
    foo.resize(size2); // should have no effect on the allocated memory
    foo.clear(); // should also have no effect on the allocated memory
    Is this behavior replicated by std::unordered_map?

    Code:
    int size1 = 100;
    int size2 = 50;
    std::unordered_map<int,int> foo;
    foo.resize(size1);
    foo.resize(size2); // will memory be released here?
    foo.clear(); // will memory be released here?

  2. #2
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    resize isn't a member of unordered_map, at least according to this reference. The next closest option, reserve(), can only be used to grow the container. You can use clear() or erase() to remove items.

  3. #3
    ¡Amo fútbol!
    Join Date
    Dec 2001
    Posts
    2,138
    Quote Originally Posted by whiteflags View Post
    resize isn't a member of unordered_map, at least according to this reference. The next closest option, reserve(), can only be used to grow the container. You can use clear() or erase() to remove items.
    Oops, my mistake. I meant reserve. Good to know that reserve doesn't release memory. While clear removes items, does it release the memory the unordered_map used to store them?

  4. #4
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by golfinguy4 View Post
    While clear removes items, does it release the memory the unordered_map used to store them?
    It is not required to, no.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  5. #5
    ¡Amo fútbol!
    Join Date
    Dec 2001
    Posts
    2,138
    But the standard doesn't dictate that it shouldn't either, correct? Ie, the release of memory is implementation-defined?

    Basically, I have a large unordered_map of POD pairs that is repeatedly cleared and refilled. I want to make sure that calling clear won't result in a ton of memory allocations on the next fill.
    Last edited by golfinguy4; 12-31-2014 at 07:52 AM.

  6. #6
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    Why don't you just use valgrind and look at the amount of allocations/deallocations? That's what I do when I'm unsure of stl stuff.

  7. #7
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by MutantJohn View Post
    Why don't you just use valgrind and look at the amount of allocations/deallocations? That's what I do when I'm unsure of stl stuff.
    Or just peek inside the class with a debugger or just step through the source code since valgrind only exists for linux.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  8. #8
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    Quote Originally Posted by Elysia View Post
    Or just peek inside the class with a debugger or just step through the source code since valgrind only exists for linux.
    That is a shame. Someone has to have ported valgrind to Windows by now, right? Or is Windows not very conducive to something like valgrind in the first place?

  9. #9
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Visual Studio has a lot of the tools valgrind offers. To my knowledge, there is no port of valgrind. I don't really pay attention to it, though, since it's a command-based tool.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  10. #10
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by golfinguy4 View Post
    But the standard doesn't dictate that it shouldn't either, correct? Ie, the release of memory is implementation-defined?
    Sure.
    Quote Originally Posted by golfinguy4 View Post
    Basically, I have a large unordered_map of POD pairs that is repeatedly cleared and refilled. I want to make sure that calling clear won't result in a ton of memory allocations on the next fill.
    If you want a guarantee, however, you're out of luck. And should look either into using a different container, or using a container differently.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  11. #11
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    Quote Originally Posted by golfinguy4 View Post
    Oops, my mistake. I meant reserve. Good to know that reserve doesn't release memory. While clear removes items, does it release the memory the unordered_map used to store them?
    I think it does, yes. (Turns out, this is wrong. See edit)

    This is some code I ran :
    Code:
    #include <iostream>
    #include <unordered_map>
    #include <utility>
    
    
    int main(void)
    {
        const int n = 50;
    
    
        std::unordered_map<int, int> map;
        map.reserve(n);
    
    
        for (int j = 0; j < 5; ++j)
        {
            for (int i = 0; i < n; ++i)
            {
                map.insert({ i, i });
            }
    
    
            map.clear();
        }
    
    
        return 0;
    }
    Compiled in 64 bit Linux with gcc 4.9.2 and : g++ -std=c++11 -Wall -Wextra -pedantic -O3

    Here's my valgrind results :

    HEAP SUMMARY:
    in use at exit: 0 bytes in 0 blocks
    total heap usage: 252 allocs, 252 frees, 8,112 bytes allocated

    All heap blocks were freed -- no leaks are possible

    As you can see, I iterate 5 * 50 = 250 times.

    Edit : I did some more testing and it seems like clear is fine. I was wrong before. But even when I get rid of the clear line, it's like using insert() or emplace() 'cause the individual allocations. Am I just inserting/emplacing elements wrong?
    Last edited by MutantJohn; 01-01-2015 at 12:24 AM.

  12. #12
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    That's not the same, John.

    What you're seeing is because the map ceases to exist (i.e. the destructor invoked). Before that happens, the "map.clear()" does not guarantee cleanup.

    It is possible to get close by calling something like this after the map.clear() calls.
    Code:
    void cleanup(std::unordered_map<int, int> &map)
    {
         std::unordered_map<int, int> temp(map.begin(), map.end(), 0);    //  0 is the initially requested (lower bound for) bucket size
         std::swap(temp, map);
       
         // temp now holds the original contents of map, so that will be released by the destructor as the function returns
    }
    The catch is that the value for the bucket count (the third argument for the constructor when creating temp) is a lower bound - an implementation is still permitted to reserve more.

    The other trade-off of this is that it creates a complete copy of the existing map, before cleaning up.


    As I said previously, if there is actually a requirement to clear/fill the map repeatedly and to keep memory down, different strategies (or different container types) are needed. However, I doubt this is a genuine requirement - it is more likely just something golfinguy has decided is a "nice to have" i.e. another form of premature optimisation.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  13. #13
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    What you're seeing is because the map ceases to exist (i.e. the destructor invoked). Before that happens, the "map.clear()" does not guarantee cleanup.
    Why is the destructor being invoked? The map declaration should be in the proper scope... right? Am I doing this wrong? What would a proper example look like that doesn't have 252 allocations/deallocations? I get the initial 2. Reserve reallocates the table which is fine. But why the extra 250?

  14. #14
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    main() provides a scope. When main() returns, your map passes out of scope and its destructor is invoked. It's actions are included in the statistics reported by valgrind.

    The details of the particular numbers depends on the implementation of insert() - and other functions related to unordered_map
    Last edited by grumpy; 01-01-2015 at 12:55 AM.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  15. #15
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    I know I should get off the net because it's New Year's Eve but this is driving me nuts. Why does something like this give me 50 allocations?
    Code:
    int main(void)
    {
        const int n = 50;
    
    
        std::unordered_map<int, int> map;
        map.reserve(n * 100);
    
    
        for (int i = 0; i < n; ++i)
        {
            std::pair<std::unordered_map<int, int>::iterator, bool> pr = map.insert({ i, i});
    
    
            std::cout << pr.first->first << " : " << pr.first->second << std::endl;
        }
    
    
        map.clear();
    
    
        return 0;
    }
    The destructor should only be called upon function exit. I'm just curious why the for-loop causes an allocation when map is in the scope above the for one. Or does each iteration of the loop change the scope? I'm so confused now and this is driving me crazy.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. implementing with map, and unordered map
    By jaesungj in forum C++ Programming
    Replies: 0
    Last Post: 11-08-2013, 09:45 AM
  2. Replies: 5
    Last Post: 06-30-2011, 10:29 PM
  3. capacity and reserve
    By George2 in forum C++ Programming
    Replies: 33
    Last Post: 03-06-2008, 08:14 PM
  4. Reserve the character in a string
    By alice in forum C Programming
    Replies: 2
    Last Post: 06-13-2004, 07:59 AM