【问题标题】:Getting Different Outputs with Recursive, Task-based, Parallel programming in TBB?在 TBB 中通过递归、基于任务的并行编程获得不同的输出?
【发布时间】:2014-05-08 13:57:39
【问题描述】:

恐怕这是一个有点长的代码。我正在使用英特尔 TBB 和 C++ 编写欧拉分区公式的并行、递归、基于任务的版本,我不认为这个程序的逻辑有很多问题,但我有一种感觉变量被错误地访问,我可能在错误的地方或其他地方声明了它们。我这样说是因为输入一个数字 n 应该总是给出相同的结果,并且它低于 n = 11,但高于它给出不同的答案。更奇怪的是,添加输出行来尝试对程序进行故障排除会产生更准确的答案(好像以某种方式填充计算的每个部分所花费的时间有助于它)。我不知道如何避免这个问题或究竟是哪个变量导致它,因为答案通常相当接近,它不仅仅是一个随机数。所以这有点棘手,我很抱歉,但如果有人可以帮助我,我将非常感激,我已经花了几个小时来解决这个问题。

这是并行任务:

class ParallelFormula : public task {
public:
int n;
int* pTot;

//Task constructor
ParallelFormula(int n_, int* pTot_) : n(n_), pTot(pTot_) {}

//Task definition
task* execute() {
    //Iterating for formula to work
    for (int k = 1; k > 0; k++) {
        //Add fixed values to pTot for any case where 2 >= n >= 0
        switch (n) {
        case 0:
            if (k % 2 != 0)
                *pTot += 1;
            else
                *pTot -= 1;
            return NULL;
        case 1:
            if (k % 2 != 0)
                *pTot += 1;
            else
                *pTot -= 1;
            return NULL;
        case 2:
            if (k % 2 != 0)
                *pTot += 2;
            else
                *pTot -= 2;
            return NULL;
        }
                    //Calculate p numbers using section of Euler's formula (relies on iteration number)
        p1 = (k*((3 * k) - 1)) / 2;
        p2 = (k*((3 * k) + 1)) / 2;
        if (n >= p2) {
            //If n is more than p2, must call recursive tasks to break down problem to smaller n's, and adds result to total result pTot (i.e. p(n))
            int x = 0;
            int y = 0;
            ParallelFormula& a = *new(allocate_child()) ParallelFormula(n - p1, &x);
            ParallelFormula& b = *new(allocate_child()) ParallelFormula(n - p2, &y);

            //Set ref_count to two children plus one for the wait
            set_ref_count(3);
            //Start b running
            spawn(b);
            //Start a running and wait for all children (a and b)
            spawn_and_wait_for_all(a);
            //Sum the total
            if (k % 2 != 0)
                *pTot += (x + y);
            else
                *pTot -= (x + y);
        }
        else if (n >= p1) {
                            //If n is more than p1, problem is small and therefore need not be parallelised, result added to pTot
            if (k % 2 != 0)
                *pTot += serialLoop(n - p1);
            else
                *pTot -= serialLoop(n - p1);
            return NULL;
        }
        else
            return NULL;
    }
}
};

调用并行任务的方法:

int parallelLoop(int n) {
int pTot = 0;
ParallelFormula& a = *new(task::allocate_root()) ParallelFormula(n, &pTot);
task::spawn_root_and_wait(a);
return pTot;
}

如果您想查看所有上下文的完整代码:

// Assignment2.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "iostream"
#include "tbb/task_scheduler_init.h"
#include "tbb/parallel_reduce.h"
#include "tbb/partitioner.h"
#include "tbb/blocked_range.h"
#include "tbb/tick_count.h"
#include "math.h"

using namespace tbb;
using namespace std;
int p, p1, p2;
int serialLoop(int n);
int n;
int m;

int serialFormula(int pTemp) {
    switch (pTemp) {
    case 0: 
        return 1;
    case 1:
        return 1;
    case 2:
        return 2;
    }
    //If p is any other value it is less than 0 and therefore has nothing to calculate - the current calculation is complete
    return 0;
}

int serialLoop(int n) {
    int pTot = 0;
    for (int k = 1; k > 0; k++) {
        //Checking whether k is even or odd to determine if adding or substracting value of p(x) to make p(n)
        if (n == 0)
            return pTot += 1;
        else if (k % 2 != 0) {
            //Calculate p number using section of Euler's formula
            p = n - ((k*((3 * k) - 1)) / 2);
            //If p is more than 2, must call recursive function to break down problem to smaller n's, and adds result to total result P (i.e. p(n))
            if (p > 2) {
                pTot += serialLoop(p);
            }
            else if (p >= 0) {
                pTot += serialFormula(p);
            }
            else return pTot;

        p = n - ((k*((3 * k) + 1)) / 2);
        if (p > 2) {
            pTot += serialLoop(p);
        }
        else if (p >= 0) {
            pTot += serialFormula(p);
        }
        else return pTot;
    }
    else {
        p = n - ((k*((3 * k) - 1)) / 2);
        if (p > 2) {
            pTot -= serialLoop(p);
        }
        else if (p >= 0) {
            pTot -= serialFormula(p);
        }
        else return pTot;

        p = n - ((k*((3 * k) + 1)) / 2);
        if (p > 2) {
            pTot -= serialLoop(p);
        }
        else if (p >= 0) {
            pTot -= serialFormula(p);
        }
        else return pTot;
    }
}
}

class ParallelFormula : public task {

public:
    int n;
    int* pTot;

//Task constructor
ParallelFormula(int n_, int* pTot_) : n(n_), pTot(pTot_) {}

//Task definition
task* execute() {
    //Checking task is called
    for (int k = 1; k > 0; k++) {
        //Calculate p number using section of Euler's formula
        switch (n) {
        case 0:
            if (k % 2 != 0)
                *pTot += 1;
            else
                *pTot -= 1;
            cout << "Case 0" << endl;
            cout << *pTot << endl;
            return NULL;
        case 1:
            if (k % 2 != 0)
                *pTot += 1;
            else
                *pTot -= 1;
            cout << "Case 1" << endl;
            cout << *pTot << endl;
            return NULL;
        case 2:
            if (k % 2 != 0)
                *pTot += 2;
            else
                *pTot -= 2;
            cout << "Case 2" << endl;
            cout << *pTot << endl;
            return NULL;
        }
        p1 = (k*((3 * k) - 1)) / 2;
        p2 = (k*((3 * k) + 1)) / 2;
        if (n >= p2) {
            //If p is more than 2, must call recursive function to break down problem to smaller n's, and adds result to total result P (i.e. p(n))
            int x = 0;
            int y = 0;
            ParallelFormula& a = *new(allocate_child()) ParallelFormula(n - p1, &x);
            ParallelFormula& b = *new(allocate_child()) ParallelFormula(n - p2, &y);

            //Set ref_count to two children plus one for the wait
            set_ref_count(3);
            //Start b running
            spawn(b);
            //Start a running and wait for all children (a and b)
            spawn_and_wait_for_all(a);
            //Sum the total
            if (k % 2 != 0)
                *pTot += (x + y);
            else
                *pTot -= (x + y);
            cout << "Double p" << endl;
            cout << *pTot << endl;
        }
        else if (n >= p1) {
            if (k % 2 != 0)
                *pTot += serialLoop(n - p1);
            else
                *pTot -= serialLoop(n - p1);
            cout << "Single p" << endl;
            cout << *pTot << endl;
            return NULL;
        }
        else
            return NULL;
    }
}
};

int parallelLoop(int n) {
    int pTot = 0;
    ParallelFormula& a = *new(task::allocate_root()) ParallelFormula(n, &pTot);
    task::spawn_root_and_wait(a);
    return pTot;
}

int main()
{
//Take inputs n and m.
cout << "Enter partition number n:" << endl;
cin >> n;

cout << "Enter modulo m:" << endl;
cin >> m;

//Start timer for serial method
tick_count serial_start = tick_count::now();

//Serial method for computing partition function modulo m.
int sP = serialLoop(n);
int serialMod = sP % m;

//Finish timer for serial method
tick_count serial_end = tick_count::now();

//Output serial results
cout << "Serial result for p(n) is: " << sP << endl;
cout << "Serial result for p(n) mod m is: " << serialMod << endl;
cout << "Serial time (s): " << (serial_end - serial_start).seconds() << endl;

//Start timer for parallel method
tick_count parallel_start = tick_count::now();

//Parallel method for computing partition function
int pP = parallelLoop(n);
int parallelMod = pP % m;

//Finish timer for parallel method
tick_count parallel_end = tick_count::now();

//Output parallel results
cout << "Parallel result for p(n) is: " << pP << endl;
cout << "Parallel result for p(n) mod m is: " << parallelMod << endl;
cout << "Parallel time (s): " << (parallel_end - parallel_start).seconds() << endl;

//Acceleration achieved
cout << "Acceleration achieved was: " << (serial_end - serial_start).seconds() / (parallel_end - parallel_start).seconds() << endl;

return 0;
};

附:这部分基于英特尔 TBB 文档中的斐波那契数列示例,所以如果我按照该示例做了一些非常愚蠢的事情,那么我也为此道歉 XD。

【问题讨论】:

  • 它给出了不同的答案对于诊断问题不是很有用。既然你知道有什么区别,不与我们分享这些似乎是不正当的,所以分享它们。但在你这样做之前,请遵循ericlippert.com/2014/03/05/how-to-debug-small-programs 中给出的建议,它(建议)是最优秀的。
  • @High Performance Mark:我很欣赏这不是一个特定的问题,让人们调试我的程序效率不高,但我对这个特殊问题已经束手无策了 XD .我会仔细阅读建议并尝试并采取行动,感谢您发布它。我已经尝试以我实际可以做到的方式分解问题并且这些部分确实有效,这似乎是并行任务运行时发生的事情,这是我真的不知道如何解决的问题。因此,任何专门针对这一编程领域的建议也会有所帮助!
  • @High Performance Mark 好的,我有一个之前应该问过的具体问题,我收到以下两个警告: - 警告 C4715: 'serialLoop' : 并非所有控制路径都返回一个值。 - 警告 C4715:“ParallelFormula::execute”:并非所有控制路径都返回值。我不完全确定为什么会触发这些警告,并且我不相信我遇到的问题被链接为非返回值肯定会阻止代码完成?但我看不出哪些可能的值最终不会返回一些东西,对吗?
  • 在 cmets 中发布的材料,顾名思义,不是问题材料。如果您想编辑您的问题,请编辑它。如果你想问一个新问题,那就问一个新问题。评论框提供的格式和排版机会太少,无法让“问题”易于理解。
  • @High Performance Mark: 公平,我想我已经发现这完全是由于我缺乏关于指针的知识,我只是在建议建议之后去正确地查看了这些知识,所以无论如何,谢谢。出于某种原因,在调试模式下运行代码摆脱了这两个警告,我仍然不知道为什么会这样。

标签: c++ parallel-processing tbb


【解决方案1】:

变量p1p2 是全局变量,但您同时在ParallelFormula::execute 中写入它们。尝试在ParallelFormula::execute 方法中声明它们,例如

int p1 = (k*((3 * k) - 1)) / 2;
int p2 = (k*((3 * k) + 1)) / 2;

也不要忘记int serialLoop(int n) 中的p 变量,因为您是从ParallelFormula::execute 调用此函数的。

【讨论】:

  • 感谢 Alex,我从来没有设法让这个程序在英特尔 TBB 上运行得特别好,但我不得不用 OpenMP 重新制定我的解决方案,并且做得更好 - 特别是因为更容易声明哪些变量是私有的或在线程之间共享,因为我设计了一个迭代解决方案,而不是我之前制造的递归混乱。
猜你喜欢
  • 1970-01-01
  • 2012-01-08
  • 2017-01-06
  • 1970-01-01
  • 2021-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多