问题不在于终止表达式,它!= vertex_set.end(),这很好。在这种情况下,termination_type 就是 std::set::iterator。
问题是 std::set 没有随机访问迭代器,因此没有 operator- 或 operator+=。换句话说,您不能在恒定时间内推进迭代器,也无法在恒定时间内计算两个迭代器之间的距离。没有合理的方法添加这些缺失的运算符。cilk_for 循环根本不打算遍历列表或集合之类的线性或树结构容器。
要理解为什么会这样,请考虑并行执行循环需要什么。首先,您必须将设置的数据分成大致相等的块,然后您必须将这些块分派给并行工作人员。循环的每次迭代都必须能够立即转到预期要处理的输入块。如果它需要线性遍历整个容器以找到其块的开始,那么您在很大程度上破坏了使用并行循环的目的。
有几种方法可以改善这种情况。首先是考虑您的集合是否足够大,以及您在其上执行的操作集是否足够昂贵,以至于根本不考虑并行性?如果没有,请使用串行循环并忘记它。如果是这样,请考虑使用不同的数据结构。你能用基于向量的数据结构替换集合吗?在网上搜索一下,你会发现 Loki 中的 AssocVector 之类的东西,它具有与 std::set 相同的接口,但在其实现中使用 vector 并具有随机访问迭代器;它应该很容易修改或包装以提供 std::set 接口而不是 std::map。它通常会胜过 std::set 或 std::map。
如果 my_functor 做了大量工作,您也许可以使用包含 spawn 的简单串行循环来分摊线性遍历的成本:
for (auto it = vertex_set.begin(); it!=vertex_set.end(); ++it) {
cilk_spawn func(*it);
}
cilk_sync;
但是请注意,如果 func() 相对较小,那么重复生成的开销将对性能产生明显的负面影响。
或者,您可以使用 cilk_for,迭代索引而不是使用迭代器,如以下代码所示:
cilk_for (std::size_t i = 0; i < vertex_set.size(); ++i) {
auto it = vertex_set.begin();
std::advance(it, i); // Slow step!
func(*it);
}
如果您的集合足够大,您可以通过将集合预分块为迭代器向量来减少对 std::advance 的调用次数。然后您可以并行迭代块:
// Each element of this vector is the start of a chunk of the set.
std::vector<std::set<vertex_id>::iterator> chunks;
// Create 1000 chunks
const auto chunk_size = vertex_set.size() / 1000;
auto chunk = vector_set.begin();
for (int i = 0; i < 1000; ++i) {
chunks.push(chunk);
advance(chunk, chunk_size);
}
chunks.push(vector_set.end());
// Now, process the chunks in parallel
cilk_for (int i = 0; i < 1000; ++i) {
// Iterate over elements in the chunk.
for (auto it = chunks[i]; it != chunks[i + 1]; ++it)
func(*it);
}
根据您的情况,上述两个主题有很多变化。确保进行一些分析和时间安排,以帮助您选择最佳方法。
最后,我将提到 cilk_for 的一个变体正在考虑中,它可以对任何可以递归细分的数据结构进行操作(就像树一样)。这样的变化将直接解决您的问题,但我不能保证何时可以使用这样的东西。