12

I need advice for micro optimization in C++ for a vector comparison function, it compares two vectors for equality and order of elements does not matter.

template <class T>
static bool compareVectors(const vector<T> &a, const vector<T> &b)
{
  int n = a.size();
  std::vector<bool> free(n, true);
  for (int i = 0; i < n; i++) {
    bool matchFound = false;
    for (int j = 0; j < n; j++) {
      if (free[j] && a[i] == b[j]) {
        matchFound = true;
        free[j] = false;
        break;
      }
    }
    if (!matchFound) return false;
  }
  return true;
}

This function is used heavily and I am thinking of possible way to optimize it. Can you please give me some suggestions? By the way I use C++11.

Thanks

ST3
  • 8,826
  • 3
  • 68
  • 92
user2381422
  • 5,645
  • 14
  • 42
  • 56
  • 1
    There's huge potential if the size of input vectors is a compile time constant. Then, a bitset rather than a `std::vector` would be better. This can even be more efficient without compile time constant size of input. Other thought: Doesn't `i <= j` suffice, so that you can start your loop over j from i? – stefan Jun 30 '13 at 19:44
  • 4
    I totally don't understand what the purpose of your "free" vector is. A somewhat unrelated thought is that you might be able to make some efficiencies happen if the order of the arrays could be assured prior to entering this function. – crowder Jun 30 '13 at 19:48
  • 1
    @crowder If there are multiple elements in `b` which compare equal, you have to find one counterpart *for each* of those in `a`. The `free` vector stores whether one element in `b` has already been associated to an element in `a`. – dyp Jun 30 '13 at 19:52
  • If we always know that a and b are the same length (your code certainly assumes this), why can't we be done as soon as we find not-matching elements? We could do that comparison without extra storage. – crowder Jun 30 '13 at 20:05
  • 2
    @user2381422 I know you said the order of the elements doesn't matter, but is it possible to order/sort the elements? (I.e. are they comparable?) If so, you could easily go from `O(n²)` to `O(n logn)` – i Code 4 Food Jun 30 '13 at 20:06
  • 4
    No matter how you optimize it otherwise, adding `if (a.size() != b.size()) return false;` to the beginning will probably help if it is common that the size of the vectors don't match. – wjl Jun 30 '13 at 20:08
  • 1
    @crowder Imagine `a` consists of `1, 1, 2, 2` and `b` consists of `1, 2, 2, 2`. In this case, there are no non-matching elements. – dyp Jun 30 '13 at 20:17
  • Certain optimizations only help for specific performance issues, e.g. cache coherency, cost of `==`, cost of copying / sorting etc. There's a chance you can achieve a better optimization by using these sort of optimizations (or another algorithm) before micro-optimizing, but we would need more info about the problem to suggest those. – dyp Jun 30 '13 at 20:31
  • Shouldn't you check that the vectors are the same size? The "for j" line seems to assume they are. – kfsone Jun 30 '13 at 22:17
  • I'd suggest you have an outer value: `lowest_j = 0;` and change the inner for to be `for(j = lowest_j; j < ...`. When you find a match, then `if(j == lowest_j) lowest_j = j + 1;`. – kfsone Jun 30 '13 at 22:21
  • 1
    **You need to give a lot more information:** About the size of the vectors, the nature of `T`, distribution of values in the vectors, how you deal with duplicate members and so on. Right now this question is broad and open-ended. – jogojapan Jul 01 '13 at 02:21
  • have you considered using a different data structure than vector? if comparing two vectors is in the hot path. you might consider using a sorted insert to make the comparison cheaper as you have to do some kind of sorting anyway? – Alexander Oh Jul 01 '13 at 11:43

7 Answers7

15

It just realized that this code only does kind of a "set equivalency" check (and now I see that you actually did say that, what a lousy reader I am!). This can be achieved much simpler

template <class T>
static bool compareVectors(vector<T> a, vector<T> b)
{
    std::sort(a.begin(), a.end());
    std::sort(b.begin(), b.end());
    return (a == b);
}

You'll need to include the header algorithm.

If your vectors are always of same size, you may want to add an assertion at the beginning of the method:

assert(a.size() == b.size());

This will be handy in debugging your program if you once perform this operation for unequal lengths by mistake.

Otherwise, the vectors can't be the same if they have unequal length, so just add

if ( a.size() != b.size() )
{
   return false;
}

before the sort instructions. This will save you lots of time.

The complexity of this technically is O(n*log(n)) because it's mainly dependent on the sorting which (usually) is of that complexity. This is better than your O(n^2) approach, but might be worse due to the needed copies. This is irrelevant if your original vectors may be sorted.


If you want to stick with your approach, but tweak it, here are my thoughts on this:

You can use std::find for this:

template <class T>
static bool compareVectors(const vector<T> &a, const vector<T> &b)
{
  const size_t n = a.size(); // make it const and unsigned!
  std::vector<bool> free(n, true);
  for ( size_t i = 0; i < n; ++i )
  {
      bool matchFound = false;
      auto start = b.cbegin();
      while ( true )
      {
          const auto position = std::find(start, b.cend(), a[i]);
          if ( position == b.cend() )
          {
              break; // nothing found
          }
          const auto index = position - b.cbegin();
          if ( free[index] )
          {
             // free pair found
             free[index] = false;
             matchFound = true;
             break;
          }
          else
          {
             start = position + 1; // search in the rest
          }
      }
      if ( !matchFound )
      {
         return false;
      }
   }
   return true;
}

Another possibility is replacing the structure to store free positions. You may try a std::bitset or just store the used indices in a vector and check if a match isn't in that index-vector. If the outcome of this function is very often the same (so either mostly true or mostly false) you can optimize your data structures to reflect that. E.g. I'd use the list of used indices if the outcome is usually false since only a handful of indices might needed to be stored.

This method has the same complexity as your approach. Using std::find to search for things is sometimes better than a manual search. (E.g. if the data is sorted and the compiler knows about it, this can be a binary search).

Ven
  • 19,015
  • 2
  • 41
  • 61
stefan
  • 10,215
  • 4
  • 49
  • 90
  • 3
    I'm not the down voter, but I can speculate on the reason for the down vote; I think it's unnecessary to keep the part before "I just realized that this code...". If you've realized that the first part of your answer is wrong or otherwise undesirable, why keep it and waste the time of future readers? – enobayram Jun 30 '13 at 20:13
  • Keep in mind `` might not be sortable, and you dont know how much memory the vectors use - copying them might be a bad idea. (The OP specifically passes them by reference in his code) – i Code 4 Food Jun 30 '13 at 20:17
  • @enobayram Thanks for pointing that out. I switched the parts because I think there's still use for that. – stefan Jun 30 '13 at 20:17
  • 2
    For comparing two entire containers, `==` is preferred over `std::equal`. – dyp Jun 30 '13 at 20:19
  • @Arthur Good point. It depends on the use-case of the OP. _Maybe_ it would be ok to have the original vectors to be sorted which certainly would be better. You're perfectly right about the uncertainties that come into play throught the template! – stefan Jun 30 '13 at 20:19
  • @DyP: _Why_? I'd presume that it's the same – stefan Jun 30 '13 at 20:20
  • @DyP Well I changed my answer accordingly, since it will obviously not do any harm. What do you mean be `free` has to be declared and defined? What `free`? – stefan Jun 30 '13 at 20:22
  • @DyP Oh now I see. I'll fix that shortly. I was confused because you didn't answer my question, but replied something ;-) So, here it is again: Why would `==` be better than `std::equal`? – stefan Jun 30 '13 at 20:25
  • 1
    I think the reason why `==` is preferred over `std::equal` is that `==` can be optimized for the specific container (the Standard library implementation most probably doesn't include a pointer to the container in the iterators). – dyp Jun 30 '13 at 20:25
  • @DyP I don't see any equality operator in libstdc++ ( http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00739.html ). How does that work? – stefan Jun 30 '13 at 20:31
  • Can't open that page, but `operator==` is a non-member function. – dyp Jun 30 '13 at 20:33
  • 6
    A micro-optimization on the first solution would be to implement an own sorting algorithm (e.g. quicksort) for the second `vector`, where you can use pivots from the first, already sorted vector. They should be optimal pivots if both are equal, and if they're not optimal (divide 50/50), the `vector`s are different (stop sorting). – dyp Jun 30 '13 at 20:47
  • @DyP That's an interesting approach, why not post this as an own answer? – stefan Jun 30 '13 at 20:59
  • @stefan Well, `std::equal` cannot check the sizes beforehand, so it loses that early-out advantage and has to put some contraints on the ranges (e.g. that the second range is not smaller than the first). Though C++14 will include a 4-argument `std::equal` which won't have those constraints and can be optimized for random access iterators. – Christian Rau Jul 01 '13 at 07:21
  • @ChristianRau I just assumed that the OPs code was correct (which assumes the length equality). I've added a small bit about how to either assert or check that before doing such heavy operations such as sort. – stefan Jul 01 '13 at 07:34
  • I don't see how the first or the second code samples are better than what I have. – user2381422 Jul 01 '13 at 10:53
  • @user2381422 I will add more on this shortly, hang on – stefan Jul 01 '13 at 10:55
  • Thank you stefan, also it would be nice if you compare your approach to GreyGeek with the unordered_map. – user2381422 Jul 01 '13 at 10:57
14

Your can probabilistically compare two unsorted vectors (u,v) in O(n):

Calculate:

U= xor(h(u[0]), h(u[1]), ..., h(u[n-1]))
V= xor(h(v[0]), h(v[1]), ..., h(v[n-1]))

If U==V then the vectors are probably equal.

h(x) is any non-cryptographic hash function - such as MurmurHash. (Cryptographic functions would work as well but would usually be slower).

(This would work even without hashing, but it would be much less robust when the values have a relatively small range).

A 128-bit hash function would be good enough for many practical applications.

Lior Kogan
  • 19,919
  • 6
  • 53
  • 85
7

I am noticing that most proposed solution involved sorting booth of the input vectors.I think sorting the arrays compute more that what is strictly necessary for the evaluation the equality of the two vector ( and if the inputs vectors are constant, a copy needs to be made). One other way would be to build an associative container to count the element in each vector... It's also possible to do the reduction of the two vector in parrallel.In the case of very large vector that could give a nice speed up.

template <typename T>  bool compareVector(const std::vector<T> &  vec1, const std::vector<T> & vec2) {
    if (vec1.size() != vec2.size())
        return false ;

    //Here we assuame that T is hashable ...
    auto count_set =  std::unordered_map<T,int>();

    //We count the element in each vector...
    for (unsigned int count = 0 ; count <  vec1.size();++count)
    {
        count_set[vec1[count]]++;
        count_set[vec2[count]]--;
    } ;

    // If everything balance out we should have zero everywhere
    return std::all_of(count_set.begin(),count_set.end(),[](const std::pair<T,int> p) { return p.second == 0 ;});

}

That way depend on the performance of your hashsing function , we might get linear complexity in the the length of booth vector (vs n*logn with the sorting). NB the code might have some bug , did have time to check it ...

Benchmarking this way of comparing two vector to sort based comparison i get on ubuntu 13.10,vmware core i7 gen 3 :

Comparing 200 vectors of 500 elements by counting takes 0.184113 seconds

Comparing 200 vectors of 500 elements by sorting takes 0.276409 seconds

Comparing 200 vectors of 1000 elements by counting takes 0.359848 seconds

Comparing 200 vectors of 1000 elements by sorting takes 0.559436 seconds

Comparing 200 vectors of 5000 elements by counting takes 1.78584 seconds

Comparing 200 vectors of 5000 elements by sorting takes 2.97983 seconds

GreyGeek
  • 918
  • 5
  • 14
  • `const std::vector& smallest_vec = (a.size() – Mooing Duck Jul 01 '13 at 01:32
  • 1
    @MooingDuck That wouldn't make sense even with `vec1` and `vec2` as the first thing this function does is `if (vec1.size() != vec2.size()) return false;`. Plus I think you still couldn't use that to initialize the `hash_map` (key-value pair). – dyp Jul 01 '13 at 01:53
  • @user2381422 most definitively this answer did not yet get enough up-votes. It's a really good approach! – stefan Jul 01 '13 at 11:00
  • 1
    This solution needs O(n) additional space and a hash function; to be efficient, the hash function has to be more efficient than a number of comparisons. You might get additional speed by supplying a bucket count. I think if the `for` loop is split into two (one over each vector), it might be possible to get better cache coherency (not sure about that one). It is an efficient solution for some cases. +1 – dyp Jul 01 '13 at 16:43
  • @DyP Very true. I did some experiment and the performance of the longer the vector to be compared the worst this method performed. Adjusting the number of bucket (which i take reduce the number of hash collision ) might indeed solve the problem. Splitting the loop might also improved performance, by better cache coherency but also because it will reduce data depency and therefore increase ILP.(the two loop might even be in different threads if the vector are long enough). Finally , i think i might possible to use something like set or map and still get better performance than sorting – GreyGeek Jul 01 '13 at 17:38
1

As others suggested, sorting your vectors beforehand will improve performance.

As an additional optimization you can make heaps out of the vectors to compare (with complexity O(n) instead of sorting with O(n*log(n)).

Afterwards you can pop elements from both heaps (complexity O(log(n))) until you get a mismatch.

This has the advantage that you only heapify instead of sort your vectors if they are not equal.

Below is a code sample. To know what is really fastest, you will have to measure with some sample data for your usecase.

#include <algorithm>

typedef std::vector<int> myvector;

bool compare(myvector& l, myvector& r)
{
   bool possibly_equal=l.size()==r.size();
   if(possibly_equal)
     {
       std::make_heap(l.begin(),l.end());
       std::make_heap(r.begin(),r.end());
       for(int i=l.size();i!=0;--i)
         {
           possibly_equal=l.front()==r.front();
           if(!possibly_equal)
             break;
           std::pop_heap(l.begin(),l.begin()+i);
           std::pop_heap(r.begin(),r.begin()+i);
         }
     }
  return possibly_equal;
}
mirk
  • 5,302
  • 3
  • 32
  • 49
0

If you use this function a lot on the same vectors, it might be better to keep sorted copies for comparison.

In theory it might even be better to sort the vectors and compare sorted vectors if each one is compared just once, (sorting is O(n*log(n)), comparing sorted vector O(n), while your function is O(n^2). But I suppose the time spent allocating memory for the sorted vectors will dwarf any theoretical gains if you don't compare the same vectors often.

As with all optimisations, profiling is the only way to make sure, I'd try some std::sort / std::equal combo.

Pieter
  • 17,435
  • 8
  • 50
  • 89
  • The memory allocation overhead could be overcome by reusing the sorted vectors between calls. This obviously needs care if multiple threads are involved. – enobayram Jun 30 '13 at 20:16
  • 1
    How can you compare two sorted vectors in O(log(n))? – Lior Kogan Jun 30 '13 at 20:30
  • 2
    @LiorKogan: That is probably a typo, but it does not really matter, does it? Whether the cost is `O(log N)` or `O(N)` (as it actually is) the algorithm is still dominated by the sorting of the vectors `O(N log N)` – David Rodríguez - dribeas Jun 30 '13 at 20:57
  • sry guys , i was in a bit of a hurry when writting the code.Everything compiles and runs on a c++11 compiler :) – GreyGeek Jul 01 '13 at 07:01
0

Like stefan says you need to sort to get better complexity. Then you can use == operator (tnx for the correction in the comments - ste equal will also work but it is more appropriate for comparing ranges not entire containers)

If that is not fast enough only then bother with microoptimization.

Also are vectors guaranteed to be of the same size? If not put that check at the begining.

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
  • 1
    I've made [a comment to stefan's answer](http://stackoverflow.com/questions/17394149/need-advice-for-micro-optimization-in-c-for-a-vector-comparison-function#comment25254433_17394298) to use `==` instead of `std::equal` because the former is preferred to compare entire containers. wjl has posted the size check in [a comment to the OP](http://stackoverflow.com/questions/17394149/need-advice-for-micro-optimization-in-c-for-a-vector-comparison-function#comment25254258_17394149) – dyp Jun 30 '13 at 22:43
-1

Another possible solution (viable only if all elements are unique), which should improve somewhat the solution of @stefan (although the complexity would remain in O(NlogN)) is this:

template <class T>
static bool compareVectors(vector<T> a, const vector<T> & b)
{
    // You should probably check this outside as it can 
    // avoid you the copy of a
    if (a.size() != b.size()) return false;

    std::sort(a.begin(), a.end());
    for (const auto & v : b)
        if ( !std::binary_search(a.begin(), a.end(), v) ) return false;
    return true;
}

This should be faster since it performs the search directly as an O(NlogN) operation, instead of sorting b (O(NlogN)) and then searching both vectors (O(N)).

Svalorzen
  • 5,353
  • 3
  • 30
  • 54