parallel_for(Inter TBB)是否有开销类似于我们在std :: function上看到的开销?

时间:2013-09-01 01:02:54

标签: c++ c++11 tbb

在这个链接std::function vs template中,讨论了std :: function的开销。基本上,为了避免由传递给std :: function构造函数的仿函数的堆分配引起的10x开销,必须使用std :: ref或std :: cref。

从@CassioNeri回答的示例,该回答显示如何通过引用将lambdas传递给std :: function。

float foo(std::function<float(float)> f) { return -1.0f * f(3.3f) + 666.0f; }
foo(std::cref([a,b,c](float arg){ return arg * 0.5f; }));

现在,英特尔®线程构建模块库使您能够并行评估循环 使用lambda / functors,如下例所示。

示例代码:

#include "tbb/task_scheduler_init.h"
#include "tbb/blocked_range.h"
#include "tbb/parallel_for.h"
#include "tbb/tbb_thread.h"
#include <vector>

int main() {
 tbb::task_scheduler_init init(tbb::tbb_thread::hardware_concurrency());
 std::vector<double> a(1000);
 std::vector<double> c(1000);
 std::vector<double> b(1000);

 std::fill(b.begin(), b.end(), 1);
 std::fill(c.begin(), c.end(), 1);

 auto f = [&](const tbb::blocked_range<size_t>& r) {
  for(size_t j=r.begin(); j!=r.end(); ++j) a[j] = b[j] + c[j];    
 };
 tbb::parallel_for(tbb::blocked_range<size_t>(0, 1000), f);
 return 0;
}

所以我的问题是:英特尔TBB parallel_for是否具有我们在std :: function上看到的相同类型的开销(编译器的堆分配)?我应该使用std :: cref通过引用parallel_for传递我的functor / lambdas以加速代码吗?

2 个答案:

答案 0 :(得分:1)

  

我应该通过引用parallel_for来使用std :: cref来加速代码来传递我的functor / lambdas吗?

我不知道你的主要问题的答案。但这并不重要,因为你永远不会那样 tbb::parallel_for

正如Cassio Neri在他的回答中指出的那样:

  

最后,请注意lambda的生命周期包含std :: function的生命周期。

他提出的问题的情况确实如此。但tbb::parallel_for不正确parallel_for整点是它将来会在任意时间从其他线程调用给定函数。

如果你通过引用给它一些仿函数,那么你必须确保这个仿函数的生命周期一直持续到parallel_for完成。否则,parallel_for可能会尝试调用对被破坏对象的引用。

那很糟糕。

因此无论可能发生什么样的开销,你都无法用引用来治愈它。

答案 1 :(得分:1)

使用std :: cref传递仿函数可能会适得其反,但我没有做出任何承诺。只有在确切的兴趣背景下进行经验测试才能确定。一般来说,对于tbb :: parallel_for,我的建议是:

  • 按值传递lambda。
  • 除非存在指示捕获模式的语义因素,否则通过引用获取lambda对象,除非它们是复制成本低的小对象。请记住,捕获的变量通常比lambda复制的次数多得多。

TBB是否支付了仿函数的堆分配成本?对于parallel_for( first ,* last *, functor )形式的签名,答案肯定是否定的,因为该表单通过引用传递了仿函数。

对于形式parallel_for( range ,* functor *)的签名,如问题所示,答案是“无需额外费用”。它不直接堆积分配仿函数。但TBB创建的每个任务都有一个函子的副本,并且任务是堆分配的(通常通过本地自由列表快速)。使用std :: cref不会改变任务堆分配的事实。使用std :: cref只会添加额外的间接级别。

我实际上有点惊讶,一种形式的tbb :: parallel_for通过引用传递函子,另一种传递值。我忘记了原因,我确信TBB小组必须对它进行辩论。选择可能是由每个引入时的可用基准和机器所推动的,或者可能是“第一个,最后一个”形式的PPL兼容性问题,这似乎不要求仿函数是可复制构造的。正如前面所暗示的那样,通过引用与通过值的性能权衡并不简单。传递引用使得仿函数更便宜,但每次访问时都会增加间接成本(除非编译器能够优化它)。

关于functor参数的生命周期,它必须在对parallel_for的调用期间存在。