我遵循了TBB的基本parallel_for example。文档说明:
模板函数parallel_for要求body对象具有复制构造函数,该构造函数被调用以为每个工作线程创建单独的副本(或多个副本)。
我的算法每个并发工作者需要一些内存才能运行。我现在在复制构造函数中分配内存。它有效,但这些是我的8线程机器上的数字:
在0-10000的范围内,我得到大约2000个工作块(调用operator()
),复制构造函数被调用大约300次!这就是问题:300个内存分配,只需要8个。我检查过只有8个线程正在运行,并且肯定不会同时使用超过8个类副本。
假设副本数量与线程数相关,我完全错了吗?有没有更好的方法来分配内存?
#include "tbb/tbb.h"
using namespace tbb;
class ApplyFoo {
float *const my_a;
public:
void operator()( const blocked_range<size_t>& r ) const {
float *a = my_a;
for( size_t i=r.begin(); i!=r.end(); ++i )
Foo(a[i]); // Foo uses the allocated memory
}
ApplyFoo( float a[] ) :
my_a(a)
{}
// the Copy-Constructor is called work every
ApplyFoo( const ApplyFoo& other ) :
my_a(a)
{
// Allocate some memory here...
}
~ApplyFoo()
{
// Free the memory here...
}
};
void ParallelApplyFoo( float a[], size_t n ) {
parallel_for(blocked_range<size_t>(0,n), ApplyFoo(a));
}
答案 0 :(得分:1)
假设副本数量与线程数相关,我完全错了吗?
您可以假设使用的默认分区程序(auto_partitioner
)具有相关性,但乘数足够大并且取决于运行时条件,因此副本数量可以与子范围数量一样大。所以,毫无疑问。
但是,可以通过指定gain-size:
来控制子范围的数量size_t p = task_scheduler_init::default_num_threads();
size_t grainsize = 2*n/p-1;
parallel_for(blocked_range<size_t>(0,n,grainsize), ApplyFoo(a));
这里的计算2*n/p-1
是因为在TBB中,粒度不是可能的子范围的最小尺寸,而是用于决定是否分割的阈值。
此外,对于具有parallel_for正文副本数量的分区程序的完全可预测行为(独立于运行时条件),请改为使用simple_partitioner
:
parallel_for(blocked_range<size_t>(0,n), ApplyFoo(a), simple_partitioner());
但是,它可能导致大范围和小粒度的额外开销,因为它不会聚合范围。
有更好的方法来分配内存吗?
是的,粒度不是一个好方法,因为它可以防止TBB调度程序更好地进行负载平衡。我建议改用thread local storage containers。与基于编译器的TLS不同,可以遍历这些值以便在一个地方清理内存,即使原始线程已经消失。