执行矢量c ++的交集

时间:2015-06-26 05:00:38

标签: c++ c++11 vector

我有200个存储在vecOfVec中的大小为1到4000000的向量。我需要将这些向量与大小为9000+元素的单个向量“vecSearched”相交。我尝试使用以下代码执行相同操作,但是使用perf工具我发现我正在做的交叉点是我的代码中的瓶颈。我是否有某种方式可以执行有效的交叉点

#include <cstdlib>
#include <iostream>
#include <vector>

using namespace std;

int main(int argc, char** argv) {
  vector<vector<unsigned> > vecOfVec; //contains 120 vectors of size ranging from 1 to 2000000 elements. All vectors in vecOfVec are sorted
  vector<unsigned> vecSearched; //vector searched in contains 9000+ elements. Vectors in vecSearched are sorted
  for(unsigned kbt=0; kbt<vecOfVec.size(); kbt++)
  {                                  
     //first find first 9 values spaced at equi-distant places, use these 9 values for performing comparisons
     vector<unsigned> equiSpacedVec;                             

     if(((vecSearched[0]))>vecOfVec[kbt][(vecOfVec[kbt].size())-1]) //if beginning of searched vector > last value present in individual vectors of vecOfVec then continue
     {
         continue;                             
     }                         

     unsigned elementIndex=0; //used for iterating over equiSpacedVec                             
     unsigned i=0; //used for iterating over individual buckets vecOfVec[kbt].second
     //search for value in bucket and store it in bucketValPos
     bool firstRun=true;             
     for(vector<unsigned>::iterator itValPos=vecSearched.begin();itValPos!=vecSearched.end();++itValPos)
     {
         //construct a summarized vector out of individual vectors of vecOfVec
         if(firstRun)
         {
             firstRun=false;
             unsigned elementIndex1=0; //used for iterating over equiSpacedVec
             while(elementIndex1<(vecOfVec[kbt].size())) //create a small vector for skipping over the remaining vectors
             {
                  if((elementIndex1+(10000))<(vecOfVec[kbt].size()))
                     elementIndex1+=10000;
                  else
                     break;
                 equiSpacedVec.push_back(vecOfVec[kbt][elementIndex1]);
             }          
         }
         //skip individual vectors of vecOfVec using summarized vector constructed above
         while((!(equiSpacedVec.empty()))&&(equiSpacedVec.size()>(elementIndex+1))&&((*itValPos)>equiSpacedVec[elementIndex+1])){
             elementIndex+=1;
             if((i+100)<(vecOfVec[kbt].size()))
                 i+=100;
         }            
         unsigned j=i;
         while(((*itValPos)>vecOfVec[kbt][j])&&(j<vecOfVec[kbt].size())){
             j++;
         }
         if(j>(vecOfVec[kbt].size()-1)) //element not found even at last position.
         {
             break;
         }              

         if((*itValPos)==vecOfVec[kbt][j])
         {                                     
             //store intersection result
         }
     }
  }
    return 0;
}

4 个答案:

答案 0 :(得分:5)

你的问题非常受欢迎。由于你没有关联矢量与交叉相关的数据,它归结为加速两个矢量之间的交集,基本上有两种方法:

1。没有任何预处理

这通常有三个方面:

  • 重复比较次数。例如,对于小向量(大小为1到50),您应该二元搜索每个元素以避免遍历主题向量的所有9000+元素。

  • 将代码质量提高到减少分支误预测,例如观察结果集通常比输入集的大小更小可以转换此类代码:

    while (Apos < Aend && Bpos < Bend) {
        if (A[Apos] == B[Bpos]) {
            C[Cpos++] = A[Apos];
            Apos++; Bpos++;
        }
        else if (A[Apos] > B[Bpos]) {
            Bpos++;
        } 
        else {
          Apos++;
        } 
    }
    

    代码“展开”这样的比较创建虽然更容易预测分支(例如块大小= 2):

    while (1) {
        Adat0 = A[Apos]; Adat1 = A[Apos + 1]; 
        Bdat0 = B[Bpos]; Bdat1 = B[Bpos + 1];
        if (Adat0 == Bdat0) { 
            C[Cpos++] = Adat0;
        }
        else if (Adat0 == Bdat1) {
            C[Cpos++] = Adat0;
            goto advanceB;
        }
        else if (Adat1 == Bdat0) {
            C[Cpos++] = Adat1;
            goto advanceA;
        }
        if (Adat1 == Bdat1) {
            C[Cpos++] = Adat1;
            goto advanceAB;
        }
        else if (Adat1 > Bdat1) goto advanceB;
        else goto advanceA;
    advanceA:
        Apos+=2;
        if (Apos >= Aend) { break; } else { continue; }
    advanceB:
        Bpos+=2;
        if (Bpos >= Bend) { break; } else { continue; }
    advanceAB:
        Apos+=2;  Bpos+=2;
        if (Apos >= Aend || Bpos >= Bend) { break; }
    }
    //  fall back to naive algorithm for remaining elements
    
  • 使用 SIMD 指令执行块操作

这些技术很难在质量检查环境中描述,但您可以阅读它们(以及if conversionherehere等相关优化,或查找实施要素{{3} }

2。预处理

这个恕我直言是一个更好的方式,因为你有一个9000+元素的单一主题向量。您可以从中创建一个区间树,或者只是找到一种方法来索引它,例如,建立一个可以加快搜索速度的结构:

vector<unsigned> subject(9000+); 
vector<range>    index(9000/M); 

其中range是类似

的结构
struct range {
    unsigned min, max; 
}; 

因此创建一系列范围,如此

[0, 100], [101, 857], ... [33221, 33500]

允许在进行交集时跳过许多比较(例如,如果另一组的元素大于子范围的最大值,则可以完全跳过该子范围)

3。并行化

是的,在两个列表中总是有第三个元素:P。当您对程序进行了足够的优化(并且仅适用于此程序)时,请将您的工作分解为块并并行运行。问题符合尴尬模式所以200个向量与1应该定义为“50对1并发4次”

测试,测量,重新设计!!

答案 1 :(得分:4)

如果我正确理解了你的代码,那么如果N是向量的数量,M是每个向量内的元素数量,那么你的算法大致为O(N * M ^ 2)。那就是&#39;桶&#39;战略改善了一些事情,但其影响很难一见钟情。

我建议你研究已排序的向量并对已排序的向量进行交叉。像这样:

vector<vector<unsigned> > vecOfVec;
vector<unsignend> vecSearched ;
for (vector<unsigned> v : vecSearched) // yes, a copy
{
   std::sort(v.begin(), v.end()) ;
   if (vecSearched.empty()) // first run detection
      vSearched = v ; 
   else
   {  // compute intersection of v and vecSearch
      auto vit = v.begin() ;
      auto vend = v.end() ;
      auto sit = vecSearched.begin() ;
      auto send = vecSearched.end() ;
      vector<unsiged> result ;
      while (vit != vend && sit != send)
      {
         if (*vit < *sit)
             vit++ ;
         else if (*vit == *sit)
         {
             result.push_bck(*it) ; 
             ++vit ;
             ++sit ;
         }
         else //  *vit > *sit
            ++sit ;
      }
      vecSearched = result ;
   }
}

代码未经测试,无论如何,它背后的想法是排序向量上的交集更容易,因为您可以比较这两个迭代器(vit,sit)并增长指向较小的迭代器。因此,M的线性是线性的,整个复杂度是O(N * M * log(M)),其中log(M)归因于排序

答案 2 :(得分:2)

比较两个排序向量的最简单方法是同时迭代它们,并且只增加迭代器具有较小值的任何值。如果两个矢量都是唯一的,则保证需要最少数量的比较。实际上,您可以对所有类型的集合链表使用相同的代码。

@Nikos Athanasiou(上面的回答)提供了很多有用的提示来加速你的代码,比如使用跳过列表,simd比较。然而,你的数据集是如此之小,以至于即使是简单易懂的天真代码也会快速地运行......

template<typename CONT1, typename CONT2, typename OP_MARKIDENTICAL>
inline
void set_mark_overlap(  const CONT1& cont1,
                        const CONT2& cont2,
                        OP_MARKIDENTICAL op_markidentical)
{
    auto ii = cont1.cbegin();
    auto end1 = cont1.cend();
    auto jj = cont2.cbegin();
    auto end2 = cont2.cend();

    if (cont1.empty() || cont2.empty())
        return;

    for (;;)
    {
        // increment iterator to container 1 if it is less
        if (*ii < *jj)
        {
            if (++ii == end1)
                break;
        }
        // increment iterator to container 2 if it is less
        else if (*jj < *ii)
        {
            if (++jj == end2)
                break;
        }
        // same values
        // increment both iterators
        else
        {
            op_markidentical(*ii);
            ++ii;
            if (ii == end1)
                break;
            // 
            // Comment if container1 can contain duplicates
            // 
            ++jj;
            if (jj == end2)
                break;
        }
    }
}

以下是使用此代码的方法:

template<typename TT>
struct op_store
{
    vector<TT>& store;
    op_store(vector<TT>& store): store(store){}
    void operator()(TT val){store.push_back(val);}
};


vector<unsigned> first{1,2,3,4,5,6};
vector<unsigned> second{1,2,5,6, 7,9};
vector<unsigned> overlap;
set_mark_overlap(   first, second, op_store<unsigned>(overlap));
for (const auto& ii : overlap)
    std::cout << ii << ",";
std::cout << ii << "\n";
// 1,2,5,6

此代码假定两个向量都不包含重复项。如果您的任何vecOfVec包含重复项,并且您希望打印出每个副本,则需要对上面显示的代码进行注释。如果您的vecSearched向量包含重复项,则不清楚适当的响应是什么......

在您的情况下,存储匹配值的代码只是这三行:

// results
vector<vector<unsigned> > results(120); 
for (unsigned ii = 0; ii < vecOfVec.size(); ++ii) 
    set_mark_overlap(vecSearched, vecOfVec[ii], op_store<unsigned>(results[ii]));

在优化方面,您的问题有两个特点: 1)一个列表总是比另一个列表短得多 2)重复使用较短的列表,而较长的列表对每次比较都是新的。

前期费用(预处理,例如@Nikos Athanasiou建议的跳过列表)与9000的短名单(一次又一次地使用)无关,但不是较长的列表。

我想大多数跳过都是针对较长的列表,所以跳过列表可能不是灵丹妙药。如何做一个动态的跳过列表,这样当容器二赶上时(在上面的代码中)你跳过N(j + = 4,000,000 / 9000)一(++ j)。如果你已经跳了两步,那么你可以使用迷你二进制搜索找到适当的数量来增加j。

由于列表长度的这种不对称性,我看不到使用SIMD进行重新编码会有所帮助:我们需要将比较次数减少到小于(N + M)而不是增加每次比较的速度。但是,这取决于您的数据。编码和时间!

以下是创建一些随机数向量并检查它们是否存在的测试代码

#include <iostream>
#include <vector>
#include <unordered_set>
#include <exception>
#include <algorithm>
#include <limits>
#include <random>

using namespace std;

template<typename TT>
struct op_store
{
    std::vector<TT>& store;
    op_store(vector<TT>& store): store(store){}
    void operator()(TT val, TT val2){if (val != val2) std::cerr << val << " !- " << val2 << "\n"; store.push_back(val);}
};




void fill_vec_with_unique_random_values(vector<unsigned>& cont, unordered_set<unsigned>& curr_values)
{
    static random_device rd;
    static mt19937 e1(rd());
    static uniform_int_distribution<unsigned> uniform_dist(0, std::numeric_limits<unsigned>::max());


    for (auto& jj : cont)
    {
        for (;;) 
        {
            unsigned new_value = uniform_dist(e1);
            // make sure all values are unique
            if (curr_values.count(new_value) == 0)
            {
                curr_values.insert(new_value);
                jj = new_value;
                break;
            }
        }
    }
}

int main (int argc, char *argv[])
{
    static random_device rd;
    static mt19937 e1(rd());

    //  vector searched in contains 9000+ elements. Vectors in vecSearched are sorted
    vector<unsigned> vecSearched(9000);
    unordered_set<unsigned> unique_values_9000;
    fill_vec_with_unique_random_values(vecSearched, unique_values_9000);

    //
    //  Create 120 vectors of size ranging from 1 to 2000000 elements. All vectors in vecOfVec are sorted
    // 
    vector<vector<unsigned> > vecOfVec(5); 
    normal_distribution<> vec_size_normal_dist(1000000U, 500000U);
    for (unsigned ii = 0; ii < vecOfVec.size(); ++ii) 
    {
        std::cerr << " Create Random data set" << ii << " ...\n";
        auto vec_size = min(2000000U, static_cast<unsigned>(vec_size_normal_dist(e1)));
        vecOfVec[ii].resize(vec_size);

        // Do NOT share values with the 9000. We will manually add these later
        unordered_set<unsigned> unique_values(unique_values_9000);
        fill_vec_with_unique_random_values(vecOfVec[ii], unique_values);
    }



    // insert half of vecSearched in our 120 vectors so that we know what we are going to find
    vector<unsigned> correct_results(begin(vecSearched), begin(vecSearched) + 4500);
    for (unsigned ii = 0; ii < vecOfVec.size(); ++ii) 
        vecOfVec[ii].insert(vecOfVec[ii].end(), begin(correct_results), end(correct_results));

    // Make sure everything is sorted
    std::cerr << " Sort data ...\n";
    for (unsigned ii = 0; ii < vecOfVec.size(); ++ii) 
        sort(begin(vecOfVec[ii]), end(vecOfVec[ii]));
    sort(begin(vecSearched), end(vecSearched));
    sort(begin(correct_results), end(correct_results));


    std::cerr << " Match ...\n";
    // results
    vector<vector<unsigned> > results(120); 
    for (unsigned ii = 0; ii < vecOfVec.size(); ++ii) 
    {
        std::cerr << ii << " done\n";
        set_mark_overlap(vecSearched, vecOfVec[ii], op_store<unsigned>(results[ii]));

        // check all is well
        if (results[ii] != correct_results)
            throw runtime_error("Oops");
    }
    return(0);
}

答案 3 :(得分:1)

使用set_intersection可能有所帮助,但我不知道,如果它提高了整体速度:

<div class="container">
    <div class="col-md-8">
        <div class="well">
            <table class="table table-hover">
                <tr>
                    <td><b>First name</b></td>
                    <td><b>last name</b></td>
                    <td><b>Age</b></td>
                    <td><b>phone</b></td>
                    <td><b>Address</b></td>
                    <td><b>D.O.J</b></td>
                </tr>
                <tr class="info">
                    <td>Rohit</td>
                    <td>Sinha</td>
                    <td>22</td>
                    <td>211232</td>
                    <td>Kandivali</td>
                    <td>22/7/12</td>
                </tr>
                <tr class="danger">
                    <td>John</td>
                    <td>Smith</td>
                    <td>40</td>
                    <td>434560</td>
                    <td>Borivali</td>
                    <td>22/7/12</td>
                </tr>
                <tr class="default">
                    <td>Kartikeya</td>
                    <td>Gupta</td>
                    <td>40</td>
                    <td>403453</td>
                    <td>Kandivali</td>
                    <td>22/7/12</td>
                </tr>
                <tr class="danger">
                    <td>John</td>
                    <td>Smith</td>
                    <td>40</td>
                    <td>234540</td>
                    <td>Kandivali</td>
                    <td>22/7/12</td>
                </tr>
            </table>
        </div>
    </div>
    <div class="col-md-4">
        <div class="well">
            <table class="table table-hover">
                <tr>
                    <td><b>First name</b></td>
                    <td><b>last name</b></td>
                    <td><b>Age</b></td>
                    <td><b>phone</b></td>
                    <td><b>Address</b></td>
                    <td><b>D.O.J</b></td>
                </tr>
                <tr class="info">
                    <td>Lalit</td>
                    <td>Bassi</td>
                    <td>22</td>
                    <td>211232</td>
                    <td>Kandivali</td>
                    <td>22/7/12</td>
                </tr>
                <tr class="danger">
                    <td>John</td>
                    <td>Smith</td>
                    <td>40</td>
                    <td>434560</td>
                    <td>Borivali</td>
                    <td>22/7/12</td>
                </tr>
                <tr class="default">
                    <td>Kartikeya</td>
                    <td>Gupta</td>
                    <td>40</td>
                    <td>403453</td>
                    <td>Kandivali</td>
                    <td>22/7/12</td>
                </tr>
                <tr class="danger">
                    <td>John</td>
                    <td>Smith</td>
                    <td>40</td>
                    <td>234540</td>
                    <td>Kandivali</td>
                    <td>22/7/12</td>
                </tr>
            </table>
        </div>
    </div>
</div>