从索引表中保留选择顺序在串行代码中是微不足道的,但在多线程中则不那么简单,特别是如果想要通过避免链表来保持效率(多线程的整点)。考虑序列号
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;
}
如何进行多线程(例如使用TBB甚至是OpenMP),尤其是data.size() < key.size()
?
答案 0 :(得分:3)
您正在寻找的并行计算操作称为Stream Compaction。
虽然算法并不重要,但它可以并行有效地实现。您最好的选择是使用已经实现它的库,例如Thrust。但是,如果你真的想自己实现,可以在GPU Programming Chapter 39.3.1中找到对算法的解释,或者在Udacity's Intro to Parallel Programming course, Lesson 4.5中找到。
基本上,它涉及为您的数组定义boolean predicate (在您的示例中,key<data.size()
),mapping将其定义为单独的数组,取{ {3}}在谓词数组上,然后执行Scan。
Map()
和Scatter()
易于并行实施; Scan()
的实施是非平凡的部分。大多数并行库将具有Scan()
实现;如果没有,上述链接都描述了几种并行扫描算法。
这就是假设你有很多内核,比如在GPU上。在CPU上,串行执行可能会更快;或者将数组划分为大块,然后串行处理块(在不同的并行核心上),并将结果合并在一起。哪种方法最好取决于您的数据(如果预期大多数键位于最终数组中,前者的效果会更好)。
答案 1 :(得分:2)
在您的主题中对您的密钥进行分区,例如有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的列表中,依此类推。但是,正如您所说的那样,避免使用链接列表是一个好主意,因为它们不能很好地并行化。
另一个选择是让每个线程将其结果存储在thead-local数组中,然后在线程完成时合并这些数组;您可以并行执行此合并(每个线程的结果大小为T {N} results.size();给定最终合并数组final_results
,T1将其数据合并到final_results[0, T1results.size()-1]
,T2合并其数据到final_results[T1results.size(), T1results.size() + T2results.size()-1]
,T3将其结果合并到final_results[T1results.size() + T2results.size(), T1results.size() + T2results.size + T3results.size()-1]
等。
另一种选择是使用TBB的共享concurrent_hash_map,其中key
为关键字,data[key]
为值。