c – 如何使用TBB多线程“尾调用”递归

我试图使用tbb多线程现有的递归算法.单线程版本使用尾调用递归,从结构上看,它看起来像这样:
void my_func() {
    my_recusive_func (0);
}

bool doSomeWork (int i,int& a,int& b,int& c) {
    // do some work
}

void my_recusive_func (int i) {
    int a,b,c;
    bool notDone = doSomeWork (i,a,c);
    if (notDone) {
        my_recusive_func (a);
        my_recusive_func (b);
        my_recusive_func (c);
    }
}

我是一个tbb新手所以我的第一次尝试使用了parallel_invoke函数

void my_recusive_func (int i) {
    int a,c);
    if (notDone) {
        tbb::parallel_invoke (
                [a]{my_recusive_func (a);},[b]{my_recusive_func (b);},[c]{my_recusive_func (c);});
    }
}

这确实有效,并且运行速度比单线程版本快,但它似乎不能很好地扩展核心数量.我所针对的机器有16个内核(32个超线程),因此可伸缩性对于这个项目非常重要,但是这个版本在该机器上最多只能获得8倍的加速,并且在算法运行时许多内核似乎处于空闲状态.

我的理论是tbb正在等待parallel_invoke之后的子任务完成所以可能有许多任务闲置等待不必要?这会解释空闲核心吗?有没有办法让父任务返回而不等待孩子?我当时想的可能是这样的,但我还不知道调度程序还不知道这是否正常:

void my_func()
{
    tbb::task_group g;
    my_recusive_func (0,g);
    g.wait();
}

void my_recusive_func (int i,tbb::task_group& g) {
    int a,c);
    if (notDone) {
        g.run([a,&g]{my_recusive_func(a,g);});
        g.run([b,&g]{my_recusive_func(b,g);});
        my_recusive_func (c,g);
    }
}

我的第一个问题是tbb :: task_group :: run()线程安全吗?我无法从文档中找到答案.此外,还有更好的方法解决这个问题吗?也许我应该使用低级调度程序调用

(我输入的代码没有编译,所以请原谅错别字.)

解决方法

这里有两个问题:

> task_group ::的TBB实现是否是线程安全的?是. (我们应该更清楚地记录这一点).
>有多个线程在同一个task_group上调用方法run()可伸缩吗?不.(我相信Microsoft文档在某处提到了这一点.)原因是task_group成为一个集中的争用点.它只是实现中的一个获取添加,但由于受影响的高速缓存行必须反弹,所以它仍然最终不可扩展.

通常最好从task_group中生成少量任务.如果使用递归并行,请为每个级别提供自己的task_group.虽然性能可能不会比使用parallel_invoke更好.

低级tbb :: task接口是最好的选择.您甚至可以使用tasK :: execute返回指向尾调用任务的指针来处理尾递归.

我有点担心空转线程.我想知道是否有足够的工作来保持线程繁忙.考虑先做work-span analysis.如果您使用的是英特尔编译器(或gcc 4.9),您可以先尝试使用cilk版本.如果这不会加速,那么即使是低级别的tbb :: task接口也不太可能有所帮助,需要检查更高级别的问题(工作和跨度).

相关文章

对象的传值与返回说起函数,就不免要谈谈函数的参数和返回值...
从实现装饰者模式中思考C++指针和引用的选择最近在看...
关于vtordisp知多少?我相信不少人看到这篇文章,多半是来自...
那些陌生的C++关键字学过程序语言的人相信对关键字并...
命令行下的树形打印最近在处理代码分析问题时,需要将代码的...
虚函数与虚继承寻踪封装、继承、多态是面向对象语言的三大特...