I need a fast STL container for finding if an element exists in it, so I tested arrays, vectors, sets, and unordered sets. I thought that sets were optimized for finding elements, because of unique and ordered values, but the fastest for 10 million iterations are: arrays (0.3 secs) vectors (1.7 secs) unordered sets (1.9 secs) sets (3 secs)
Here is the code:
#include <algorithm>
#include <iostream>
#include <set>
#include <unordered_set>
#include <vector>
int main() {
using std::cout, std::endl, std::set, std::unordered_set, std::vector, std::find;
int i;
const long ITERATIONS = 10000000;
int a[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
for (int i = 0; i < ITERATIONS; i ) {
if (find(a, a 16, rand() % 64) == a 16) {}
else {}
}
vector<int> v{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
for (i = 0; i < ITERATIONS; i ) {
if (find(v.begin(), v.end(), rand() % 64) == v.end()) {}
else {}
}
set<int> s({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
for (i = 0; i < ITERATIONS; i ) {
if (find(s.begin(), s.end(), rand() % 64) == s.end()) {}
else {}
}
unordered_set<int> us({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
for (i = 0; i < ITERATIONS; i ) {
if (find(us.begin(), us.end(), rand() % 64) == us.end()) {}
else {}
}
}
CodePudding user response:
Please remember that in C
and C
there is the as if rule!
This means compiler can transform code by any means (even by dropping code) as long as observable result of running code remains unchanged.
Here is godbolt of your code.
Now note what compiler did for if (find(a, a 16, rand() % 64) == a 16) {}
:
.L206:
call rand
sub ebx, 1
jne .L206
Basically compiler noticed that result of it is not used and remove everything expect calling rand()
which has side effects (visible changes in results).
The same happen for std::vector
:
.L207:
call rand
sub ebx, 1
jne .L207
And even for std::set
and std::unordered_set
compiler was able to perform same optimization. The difference you are seeing (you didn't specified how you did that) is just result of initializing all of this variables which is time consuming for more complex containers.
Writing good performance test is hard and should be approached with caution.
There is also second problem with your question. Time complexity of given code.
Searching array and searching std::set
and std::unrodered_set
scales differently to size of data. For small data set simple array will be fates since its simple implementation and optimal access to memory. As data size grow time complexity of std::find
for array will grow as O(n)
on other hand slower std::set
time to find item will grow as O(log n)
and for std::unordered_set
it will be constant time O(1)
. So for small amount of data array will be fastest, for medium size std::set
is the winner and if amount of data will be large std::unordered_set
will be best.
Take a look on this benchmark example which uses google benchmark.
CodePudding user response:
You are not measuring efficiency, you are measuring performance. And doing so badly.
The effect of address space randomization or just different usernames or other variable in env
has up to about 40% effect on speed. That's more than the difference between -O0 and -O2. You are only measuring one single system with one single address space layout 10000000 times. That makes the value about meaningless.
And yet you still manage to figure out that for 16 ints any attempt to be better than just looking at all of them will perform worse. A simple linear search in a single cache line (or two if the layout is bad, more likely) is just simply the best way.
Now try again with 10000000 ints and run the binary 1000 times. Even better if you use a layout randomizer to truly exclude accidents of the layout from the timing.
Note: Iirc the limit for sort where an array with bubble sort is best is somewhere between 32 and 64 ints and find is even simpler.