簡體   English   中英

如何使用TBB多線程“尾調用”遞歸

[英]How to multithread “tail call” recursion using 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, b, 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, b, c;
    bool notDone = doSomeWork (i, a, b, 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, b, c;
    bool notDone = doSomeWork (i, a, b, 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()線程安全嗎? 我無法從文檔中找到答案。 此外,還有更好的方法來解決這個問題嗎? 也許我應該使用低級調度程序調用?

(我輸入的代碼沒有編譯,所以請原諒錯別字。)

我很相信tbb::task_group::run()是線程安全的。 我在文檔中找不到提及,這是相當令人驚訝的。

然而,

  • 這篇2008年的博客文章包含了task_group的原始實現,其run()方法被明確指出是線程安全的。 目前的實施非常相似。
  • tbb::task_group的測試代碼(在src/test/test_task_group.cpp )帶有一個測試,用於測試task_group的線程安全性(它產生一堆線程,每個線程調用run()一千次或者更多關於同一task_group對象)。
  • TBB附帶的sudoku示例代碼(在examples/task_group/sudoku/sudoku.cpp )也從遞歸函數中的多個線程調用task_group::run ,基本上與您提出的代碼相同。
  • task_group是TBB和Microsoft的PPL之間共享的功能之一,其task_group線程安全的 雖然TBB文檔提醒說TBB和PPL版本之間的行為仍然存在差異,但如果線程安全(因此需要外部同步)不同的話,那將是非常令人驚訝的。
  • tbb::structured_task_group (描述為“類似於task_group ,但只有一部分功能”)具有明確的限制,即“方法runrun_and_waitcancelwait應僅由創建structured_task_group的線程調用”。

這里有兩個問題:

  1. task_group :: TBB的TBB實現是否是線程安全的? 是。 (我們應該更清楚地記錄這一點)。
  2. 有多個線程在同一個 task_group上調用方法run()可伸縮嗎? 不。(我相信Microsoft文檔在某處提到了這一點。)原因是task_group成為一個集中的爭用點。 它只是實現中的一個獲取和添加,但由於受影響的高速緩存行必須反彈,所以最終仍然是不可擴展的。

通常最好從task_group中生成少量任務。 如果使用遞歸並行,請為每個級別提供自己的task_group。 雖然性能可能不會比使用parallel_invoke更好。

低級tbb :: task接口是最好的選擇。 您甚至可以使用tasK :: execute返回指向尾調用任務的指針的技巧來編寫尾遞歸。

但我有點擔心空轉線程。 我想知道是否有足夠的工作來保持線程繁忙。 首先考慮進行工作范圍分析 如果您使用的是英特爾編譯器(或gcc 4.9),您可以先嘗試使用Cilk版本。 如果這不會加速,那么即使是低級別的tbb :: task接口也不太可能有所幫助,需要檢查更高級別的問題(工作和跨度)。

您也可以按如下方式實現:

constexpr int END = 10;
constexpr int PARALLEL_LIMIT = END - 4;
static void do_work(int i, int j) {
    printf("%d, %d\n", i, j);
}

static void serial_recusive_func(int i, int j) {
    // DO WORK HERE
    // ...
    do_work(i,j);
    if (i < END) {
        serial_recusive_func(i+1, 0);
        serial_recusive_func(i+1, 1);
        serial_recusive_func(i+1, 2);
    }
}

class RecursiveTask : public tbb::task {
    int i;
    int j;
public:
    RecursiveTask(int i, int j) :
        tbb::task(),
        i(i), j(j)
    {}
    task *execute() override {
        //DO WORK HERE
        //...
        do_work(i,j);
        if (i >= END) return nullptr;
        if (i < PARALLEL_LIMIT) {
            auto &c = *new (allocate_continuation()) tbb::empty_task();
            c.set_ref_count(3);
            spawn(*new(c.allocate_child()) RecursiveTask(i+1, 0));
            spawn(*new(c.allocate_child()) RecursiveTask(i+1, 1));
            recycle_as_child_of(c);
            i = i+1; j = 2;
            return this;
        } else {
            serial_recusive_func(i+1, 0);
            serial_recusive_func(i+1, 1);
            serial_recusive_func(i+1, 2);
        }
        return nullptr;
    }
};
static void my_func()
{
    tbb::task::spawn_root_and_wait(
        *new(tbb::task::allocate_root()) RecursiveTask(0, 0));
}
int main() {
    my_func();
}

你的問題沒有包含很多關於“在這里工作”的信息,所以我的實現並沒有給do_work很多機會來返回值或影響遞歸。 如果您需要,您應該編輯您的問題,以便提及“在這里工作”會對整體計算產生什么樣的影響。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM