我有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;
}
答案 0 :(得分:5)
你的问题非常受欢迎。由于你没有关联矢量与交叉相关的数据,它归结为加速两个矢量之间的交集,基本上有两种方法:
这通常有三个方面:
重复比较次数。例如,对于小向量(大小为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 conversion
)here和here等相关优化,或查找实施要素{{3} }
这个恕我直言是一个更好的方式,因为你有一个9000+元素的单一主题向量。您可以从中创建一个区间树,或者只是找到一种方法来索引它,例如,建立一个可以加快搜索速度的结构:
vector<unsigned> subject(9000+);
vector<range> index(9000/M);
其中range是类似
的结构struct range {
unsigned min, max;
};
因此创建一系列范围,如此
[0, 100], [101, 857], ... [33221, 33500]
允许在进行交集时跳过许多比较(例如,如果另一组的元素大于子范围的最大值,则可以完全跳过该子范围)
是的,在两个列表中总是有第三个元素: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>