C++ 索引表保序选择的并行算法

C++ 索引表保序选择的并行算法,c++,multithreading,algorithm,openmp,tbb,C++,Multithreading,Algorithm,Openmp,Tbb,从索引表中保留顺序的选择在串行代码中很简单,但在多线程中就不那么简单了,特别是如果希望通过避免链表来保持效率(多线程的整个要点)。考虑串行代码 template<typename T> std::vector<T> select_in_order( std::vector<std::size_t> const&keys, // permutation of 0 ... key.size()-1 std::vector<T> cons

从索引表中保留顺序的选择在串行代码中很简单,但在多线程中就不那么简单了,特别是如果希望通过避免链表来保持效率(多线程的整个要点)。考虑串行代码

template<typename T>
std::vector<T> select_in_order(
  std::vector<std::size_t> const&keys, // permutation of 0 ... key.size()-1
  std::vector<T> const&data)           // anything copyable
{ // select data[keys[i]] allowing keys.size() >= data.size()
  std::vector<T> result;
  for(auto key:keys)
    if(key<data.size())
      result.push_back(data[key]);
  return result;
}
模板
std::矢量按顺序选择(
std::vector const&keys,//0的置换…key.size()-1
std::vector const&data)//任何可复制的内容
{//select data[keys[i]]允许keys.size()>=data.size()
std::向量结果;
用于(自动关键点:关键点)

如果(key在线程之间划分密钥,例如,使用N个线程,您将给T1密钥{0,key.size()/N-1},T2获取密钥{key.size()/N,2*key.size()/N-1},等等,TN获取密钥{(N-1)/N*keys.size(),keys.size()-1}。每个线程将其结果放入线程本地容器中,当线程完成后,您可以合并容器。这样,您就不需要在线程之间执行任何同步

合并容器的最有效方法是将容器作为链表,因为很容易将T2的列表附加到T1的列表,等等。但是,正如您所说的,避免链表是一个好主意,因为它们不能很好地并行化

另一种选择是让每个线程将其结果存储在一个AD本地数组中,然后在线程完成后合并这些数组;您可以并行执行此合并(每个线程的结果大小为T{N}results.size();给定最终合并数组
final_results
,T1将其数据合并到
final_results[0,T1results.size()-1]
,T2将其数据合并到
最终结果[T1results.size(),T1results.size()+T2results.size()-1]
,T3将其结果合并到
最终结果[T1results.size()+T2results.size(),T1results.size()+T2results.size()+T3results.size()-1]


另一种选择是使用来自TBB的共享密钥,以
key
作为密钥,以
data[key]
作为值。

在线程之间划分密钥,例如,对于N个线程,您将给T1密钥{0,key.size()/N-1},T2获取密钥{key.size()/N,2*key.size()/N-1},等等,TN获取密钥{(N-1)/N*keys.size(),keys.size()-1}。每个线程将其结果放入一个线程本地容器中,在线程完成后合并容器。这样,就不需要在线程之间执行任何同步

合并容器的最有效方法是将容器作为链表,因为很容易将T2的列表附加到T1的列表,等等。但是,正如您所说的,避免链表是一个好主意,因为它们不能很好地并行化

另一种选择是让每个线程将其结果存储在一个AD本地数组中,然后在线程完成后合并这些数组;您可以并行执行此合并(每个线程的结果大小为T{N}results.size();给定最终合并数组
final_results
,T1将其数据合并到
final_results[0,T1results.size()-1]
,T2将其数据合并到
最终结果[T1results.size(),T1results.size()+T2results.size()-1]
,T3将其结果合并到
最终结果[T1results.size()+T2results.size(),T1results.size()+T2results.size()+T3results.size()-1]


另一个选项是使用共享的from TBB,其中
key
作为键,
data[key]
作为值。

调用您正在寻找的并行计算操作

它可以高效地并行实现,尽管算法非常复杂。您最好使用一个已经实现了它的库,例如。如果您确实想自己实现,可以在中或中找到算法的解释


本质上,它涉及为数组定义一个函数(在您的示例中,
key调用您正在寻找的并行计算操作)

它可以高效地并行实现,尽管算法非常复杂。您最好使用一个已经实现了它的库,例如。如果您确实想自己实现,可以在中或中找到算法的解释


本质上,它涉及到为数组定义一个数组(在您的示例中,
keyhmmm我必须考虑这些选项。这里肯定有一些想法我没有考虑过。我如何使用tbb实现您的方法?我不能使用
parallel\u for()
,因为这样每个线程都会有一个随机的键集合来处理,而不是一个有序的块。@Walter使用嵌套循环;外循环是循环
i=0到键的平行循环。size
键递增。size/N
(其中N是您想要的线程数),而内循环是循环
j=i到键的标准(i+keys.size/N)
递增1。使用数组数组存储结果:长度为N的数组,长度为
keys.size/N
。在外循环
k=0到N
中使用第二个计数器,并让内循环将其结果存储在
数组[k]索引的数组中
。要合并结果数组,您只需为loop@Walter您可以使用向量数组,也可以使用
数组[i/(key.size/N)]
来代替第二个计数器。如何控制
N
使用的线程数
?嗯,我必须考虑这些选项。这里肯定有一些想法我没有考虑过。我如何使用tbb实现您的方法?我不能使用()
,因为这样每个线程都将有一个随机的键集合来处理,而不是一个有序的块。@Walter使用嵌套循环;外部循环是一个并行的循环,用于循环
i=0到键。size
键递增。size/N
(其中N是N)