tags:

views:

125

answers:

5

How large does a collection have to be for std::map to outpace a sorted std::vector >?

I've got a system where I need several thousand associative containers, and std::map seems to carry a lot of overhead in terms of CPU cache. I've heard somewhere that for small collections std::vector can be faster -- but I'm wondering where that line is....

EDIT: I'm talking about 5 items or fewer at a time in a given structure. I'm concerned most with execution time, not storage space. I know that questions like this are inherently platform-specific, but I'm looking for a "rule of thumb" to use.

Billy3

+1  A: 

If you say "outspace" you mean consuming more space (aka memory), then it's very likely that vector will always be more efficient (the underlying implementation is an continous memory array with no othe data, where map is a tree, so every data implies using more space). This however depends on how much the vector reserves extra space for future inserts.

When it is about time (and not space), vector will also always be more effective (doing a dichotomic search). But it will be extreamly bad for adding new elements (or removing them).

So : no simple answer ! Look-up the complexities, think about the uses you are going to do. http://www.cplusplus.com/reference/stl/

Tristram Gräbener
+1 for std::vector you have to sort at each insert.
Nikko
@Nikko: You can insert everything at the right place without any sorting (`lower_bound + insert`), but yes, unless there will be insertions and deletions, a sorted vector is always better than a map.
UncleBens
Maybe it's better to use a std::list?
Nikko
@Nikko: A list is almost never good for anything. It has similar memory allocation issues as a map (each node is a separate allocation), and it doesn't provide any useful kind of lookup to compete with the others.
UncleBens
out /pace/, not "outspace". To "outpace" is to do better than [something], not necessarily in size (often in speed).
Ricket
+6  A: 

It's not really a question of size, but of usage.

A sorted vector works well when the usage pattern is that you read the data, then you do lookups in the data.

A map works well when the usage pattern involves a more or less arbitrary mixture of modifying the data (adding or deleting items) and doing queries on the data.

The reason for this is fairly simple: a map has higher overhead on an individual lookup (thanks to using linked nodes instead of a monolithic block of storage). An insertion or deletion that maintains order, however, has a complexity of only O(lg N). An insertion or deletion that maintains order in a vector has a complexity of O(N) instead.

There are, of course, various hybrid structures that can be helpful to consider as well. For example, even when data is being updated dynamically, you often start with a big bunch of data, and make a relatively small number of changes at a time to it. In this case, you can load your data into memory into a sorted vector, and keep the (small number of) added objects in a separate vector. Since that second vector is normally quite small, you simply don't bother with sorting it. When/if it gets too big, you sort it and merge it with the main data set.

Edit2: (in response to edit in question). If you're talking about 5 items or fewer, you're probably best off ignoring all of the above. Just leave the data unsorted, and do a linear search. For a collection this small, there's effectively almost no difference between a linear search and a binary search. For a linear search you expect to scan half the items on average, giving ~2.5 comparisons. For a binary search you're talking about log2 N, which (if my math is working this time of the morning) works out to ~2.3 -- too small a difference to care about or notice (in fact, a binary search has enough overhead that it could very easily end up slower).

Jerry Coffin
This. If your container only has five items in t, then whatever you want won't make a difference.
DeadMG
A: 

EDIT: Seeing as you're talking about 5 items or fewer:

Sorting involves swapping items. When inserting into std::map, that will only involve pointer swaps. Whether a vector or map will be faster depends on how fast it is to swap two elements.


I suggest you profile your application to figure it out.


If you want a simple and general rule, then you're out of luck - you'll need to consider at least the following factors:

Time

  • How often do you insert new items compared to how often you lookup?
  • Can you batch inserts of new items?
  • How expensive is sorting you vector? Vectors of elements that are expensive to swap become very expensive to sort - vectors of pointers take far less.

Memory

  • How much overhead per allocation does the allocator you're using have? std::map will perform one allocation per item.
  • How big are your key/value pairs?
  • How big are your pointers? (32/64 bit)
  • How fast does you implementation of std::vector grow? (Popular growth factors are 1.5 and 2)

Past a certain size of container and element, the overhead of allocation and tree pointers will become outweighed by the cost of the unused memory at the end of the vector - but by far the easiest way to find out if and when this occurs is by measuring.

Joe Gauterin
A: 

It has to be in the millionth items. And even there ...

I am more thinking here to memory usage and memory accesses. Under hundreds of thousands, take whatever you want, there will be no noticeable difference. CPUs are really fast these days, and the bottleneck is memory latency.

But even with millions of items, if your map<> has been build by inserting elements in random order. When you want to traverse your map (in sorted order) you'll end up jumping around randomly in the memory, stalling the CPU for memory to be available, resulting in poor performance.

On the other side, if your millions of items are in a vector, traversing it is really fast, taking advantage of the CPU memory accesses predictions.

As other have written, it depends on your usage.

Edit: I would more question the way to organize your thousands of associative containers than the containers themselves if they contain only 5 items.

Didier Trosset
Each container is associated with a file. It contains blobs of data which are associated with that file, such as MD5 hash. The map lookup saves calculation of the MD5 for that file over again. This application walks the directory tree to look for "interesting" items on the filesystem. Thus, each file needs only perhaps 5 items at a time, one for attributes, one for MD5, one for.... etc. But since there are thousands of files, the performance of the container becomes significant.
Billy ONeal
This is exactly what I mean. Don't bother about the container as it has only few items of small size, use vector for its simplicity and predictive behavior (as opposed to map where behavior and performance depends on whether the various items are allocated in contiguous memory locations or not. This does make a difference!). Pay more attention to your container of containers.
Didier Trosset
@Didier Trosset: My container of containers does not matter because few files are actually in memory at any one time -- one, to be specific. (Unless things need to be sorted, in which case I'm using a deque) The reason for the container is that there are ~35 types of info I'd want to associate with a file, but putting space for each one of those in the class associated with a file has resulted in that class A. doing too much and B. being too large. I expect about 5 to be used at a time but it's entirely possible for it to be more.
Billy ONeal
A: 

The main issue with std::map is an issue of cache, as you pointed.

The sorted vector is a well-known approach: Loki::AssocVector.

For very small datasets, the AssocVector should crush the map despite the copy involved during insertion simply because of cache locality. The AssocVector will also outperform the map for read-only usage. Binary search is more efficient there (less pointers to follow).

For all other uses, you'll need to profile...

There is however an hybrid alternative that you might wish to consider: using the Allocator parameter of the map to restrict the memory area where the items are allocated, thus minimizing the locality reference issue (the root of cache misses).

There is also a paradigm shift that you might consider: do you need sorted items, or fast look-up ?

In C++, the only STL-compliant containers for fast-lookup have been implemented in terms of Sorted Associative Containers for years. However the up-coming C++0x features the long awaited unordered_map which could out perform all the above solutions!

Matthieu M.
Unordered map is not usable in my implementation because A. it requires a hashing algorithm and B. it's memory overhead is too high. As for the allocator, most any common implementation of the STL already does this for the standard associative containers. I doubt anything you or I would write is going to perform better than Dinkumware's or SGI's implementation.
Billy ONeal