5

I was using a map in some code to store ordered data. I found out that for huge maps, destruction could take a while. In this code I had, replacing map by vector<pair> reduced processing time by 10000...

Finally, I was so surprised that I decided to compare map performances with sorted vector or pair.

And I'm surprised because I could not find a situation where map was faster than a sorted vector of pair (filled randomly and later sorted)...there must be some situations where map is faster....else what's the point in providing this class?

Here is what I tested:

Test one, compare map filling and destroying vs vector filling, sorting (because I want a sorted container) and destroying:

#include <iostream>
#include <time.h>
#include <cstdlib>
#include <map>
#include <vector>
#include <algorithm>

int main(void)
{

    clock_t tStart = clock();

    {
        std::map<float,int> myMap;
        for ( int i = 0; i != 10000000; ++i )
        {
            myMap[ ((float)std::rand()) / RAND_MAX ] = i;
        }
    }

    std::cout << "Time taken by map: " << ((double)(clock() - tStart)/CLOCKS_PER_SEC) << std::endl;

    tStart = clock();

    {
        std::vector< std::pair<float,int> > myVect;
        for ( int i = 0; i != 10000000; ++i )
        {
            myVect.push_back( std::make_pair( ((float)std::rand()) / RAND_MAX, i ) );
        }

        // sort the vector, as we want a sorted container:
        std::sort( myVect.begin(), myVect.end() );
    }

    std::cout << "Time taken by vect: " << ((double)(clock() - tStart)/CLOCKS_PER_SEC) << std::endl;

    return 0;
}

Compiled with g++ main.cpp -O3 -o main and got:

Time taken by map: 21.7142
Time taken by vect: 7.94725

map's 3 times slower...

Then, I said, "OK, vector is faster to fill and sort, but search will be faster with the map"....so I tested:

#include <iostream>
#include <time.h>
#include <cstdlib>
#include <map>
#include <vector>
#include <algorithm>

int main(void)
{
    clock_t tStart = clock();

    {
        std::map<float,int> myMap;
        float middle = 0;
        float last;
        for ( int i = 0; i != 10000000; ++i )
        {
            last = ((float)std::rand()) / RAND_MAX;
            myMap[ last ] = i;
            if ( i == 5000000 )
                middle = last; // element we will later search
        }

        std::cout << "Map created after " << ((double)(clock() - tStart)/CLOCKS_PER_SEC) << std::endl;

        float sum = 0;
        for ( int i = 0; i != 10; ++i )
            sum += myMap[ last ]; // search it

        std::cout << "Sum is " << sum << std::endl;
    }

    std::cout << "Time taken by map: " << ((double)(clock() - tStart)/CLOCKS_PER_SEC) << std::endl;

    tStart = clock();

    {
        std::vector< std::pair<float,int> > myVect;
        std::pair<float,int> middle;
        std::pair<float,int> last;
        for ( int i = 0; i != 10000000; ++i )
        {
            last = std::make_pair( ((float)std::rand()) / RAND_MAX, i );
            myVect.push_back( last );
            if ( i == 5000000 )
                middle = last; // element we will later search
        }

        std::sort( myVect.begin(), myVect.end() );

        std::cout << "Vector created after " << ((double)(clock() - tStart)/CLOCKS_PER_SEC) << std::endl;

        float sum = 0;
        for ( int i = 0; i != 10; ++i )
            sum += (std::find( myVect.begin(), myVect.end(), last ))->second; // search it

        std::cout << "Sum is " << sum << std::endl;
    }

    std::cout << "Time taken by vect: " << ((double)(clock() - tStart)/CLOCKS_PER_SEC) << std::endl;

    return 0;
}

Compiled with g++ main.cpp -O3 -o main and got:

Map created after 19.5357
Sum is 1e+08
Time taken by map: 21.41
Vector created after 7.96388
Sum is 1e+08
Time taken by vect: 8.31741

Even search is apparently faster with the vector (10 searchs with the map took almost 2sec and it took only half a second with the vector)....

So:

  • Did I miss something?
  • Is my tests not correct/accurate?
  • Is map simply a class to avoid or is there really situations where map offers good performances?
jpo38
  • 20,821
  • 10
  • 70
  • 151
  • 3
    Large amount of insertions in already existing container when you need to maintain order between operations (for example there are lookups between insertions). Try to insert 10k random values into map and into sorted vector. – Revolver_Ocelot Feb 12 '16 at 21:01
  • You've hardly tested anything (e.g.: what about removing elements?). But yes, in general node based data structures are very cache-unfriendly. – Karoly Horvath Feb 12 '16 at 21:06
  • @Revolver_Ocelot: Thanks, that was the point and I missed that. Did a test (see answer below), and yes, `map` gets finally faster. – jpo38 Feb 12 '16 at 21:07
  • @KarolyHorvath - unless you use memory pools for node allocation. Well, it will still not be as good as sequential data, but significantly better than just having nodes spread in memory. – dtech Feb 12 '16 at 21:08
  • You may also want to consider unordered_map, see this [Scott Meyers blog post](http://scottmeyers.blogspot.ca/2015/09/should-you-be-using-something-instead.html). – zdan Feb 12 '16 at 21:10
  • 1
    `10 searchs with the map took almost 2sec` don't you think that is suspicious? That number cannot be true because it is too slow by like 10000x. – usr Feb 12 '16 at 21:12
  • Can you post a benchmark that measures just searching and not just 10 times? Right now you are measuring building the collection 99.999% of the time. – usr Feb 12 '16 at 21:13
  • One problem with this benchmark is that always looking for the same element causes memory latency to disappear because all data accessed will be cached. That's probably not realistic. – usr Feb 12 '16 at 21:15
  • @jpo38 - you should really be more diligent with testing before you hurry to make conclusions. – dtech Feb 12 '16 at 21:16
  • @usr: Good point, with a 100 elements search, `map` is way faster than the `vector`. – jpo38 Feb 12 '16 at 21:39
  • You should search for 1m elements which should take a few seconds. That would be a valid benchmark. Not sure how you obtained any valid number with 100 lookups which should have taken milliseconds. – usr Feb 12 '16 at 22:01
  • @usr: True, some results are surprising....but I can't explain why... – jpo38 Feb 12 '16 at 22:20
  • Then please post representative and valid benchmark code. I'll vote to close for now since nobody knows what you did. I might retract that close vote. – usr Feb 12 '16 at 22:22
  • @usr: Posted this as an answer, as it shows a case where `map` is faster than `vector`. – jpo38 Feb 12 '16 at 23:00
  • Your ordered vector search is not appropriate here. Don't use `std::find`, which has linear complexity, but rather `std::lower_bound` which as the map search has logarithmic complexity. – davidhigh Feb 12 '16 at 23:52

2 Answers2

5

Generally a map will be better when you're doing a lot of insertions and deletions interspersed with your lookups. If you build the data structure once and then only do lookups, a sorted vector will almost certainly be faster, if only because of processor cache effects. Since insertions and deletions at arbitrary locations in a vector are O(n) instead of O(log n), there will come a point where those will become the limiting factor.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Not sure about that. Both algorithms have log N time complexity and log N cache misses. Might be roughly equal. – usr Feb 12 '16 at 23:48
  • @usr the vector has all its data stored in consecutive addresses; if the data is smaller than a cache line then the final couple iterations will benefit from that, and later iterations are less likely to push the earlier ones out of the cache for next time. – Mark Ransom Feb 12 '16 at 23:53
  • @MarkRansom: Thanks for the clarification. In my situation, the container will have new elements be often inserted, but there will always be inserted at the end of the container (my key is actually the timestamp of the objects). So even then, `vector` will most likely remain faster than `map` upon insertion. Finally, I'll use a binary search rather than a find to optimize search (as proposed here: http://stackoverflow.com/questions/446296/where-can-i-get-a-useful-c-binary-search-algorithm). – jpo38 Feb 13 '16 at 08:49
  • @jpo38 I didn't realize you weren't using a binary search already, since you explicitly said you were using a sorted vector... I'm surprised that `find` was faster! – Mark Ransom Feb 13 '16 at 13:27
  • @MarkRansom: With more searchs it gets slower....I posted this as an answer but removed it because being downvoted for it....:-( – jpo38 Feb 13 '16 at 18:36
1

std::find has linear time complexity whereas a map search has log N complexity.

When you find that one algorithm is 100000x faster than the other you should get suspicious! Your benchmark is invalid.

You need to compare realistic variants. Probably, you meant to compare map with a binary search. Run each of those variants for at least 1 second of CPU time so that you can realistically compare the results.

When a benchmark returns "0.00001 seconds" time spent you are well in the clock inaccuracy noise. This number means nothing.

usr
  • 168,620
  • 35
  • 240
  • 369