使用任务

时间:2017-05-23 12:46:53

标签: c# algorithm recursion parallel-processing task

如何使用任务将此顺序递归算法转换为并行递归算法?

public static List<int> s = new List<int>(); // random integers, array size is n
public static List<int> p = new List<int>(); // random integers, array size is n
public static int n = 100;
public static int w = 10;

static void Main(string[] args)
{
    G1(n, w)
}

private static int G1(int k, int r)
{
    if (k == 0 || r == 0)
    {
        return 0;
    }

    if (s[k - 1] > r)
    {
        return G1(k - 1, r);
    }

    return Math.Max(G1(k - 1, r), p[k - 1] + G1(k - 1, r - s[k - 1]));
}

我有一个例子,但它太混乱了,它没有解释,也没有我的情况那么复杂。

class CustomData
{
    public int TNum;
    public int TResult;
}

static int F1(int n)
{
    if (n > 1) return F1(n - 1) + F1(n - 2);
    else return 1;
}

static int F2(int n)
{
    int fibnum = 0;
    if (n < 6) fibnum = F1(n);
    else
    {
    //fibnum = F1(n - 3) + 3 * F1(n - 4) + 3 * F1(n - 5) + F1(n - 6);
    int countCPU = 4;
    Task[] tasks = new Task[countCPU];
    for (var j = 0; j < countCPU; j++)
    tasks[j] = Task.Factory.StartNew(
    (Object pp) =>
    {
        var data = pp as CustomData; if (data == null) return;
        data.TResult = F1(n - data.TNum - 3);
    },
    new CustomData() {TNum = j });
    Task.WaitAll(tasks);
    fibnum = (tasks[0].AsyncState as CustomData).TResult + 3 * (tasks[1].AsyncState as CustomData).TResult + 3 * (tasks[2].AsyncState as CustomData).TResult + (tasks[3].AsyncState as CustomData).TResult;
    }
    return fibnum;
}

2 个答案:

答案 0 :(得分:1)

对于像这样的简单任务,并行化的开销相当高,所以你绝对不希望天真地并行化每次迭代。但是,样本任务相当复杂,不仅要将递归函数更改为(某种程度上)并行化的递归函数,还要找到额外的并行化选项以获得四个独立的工作者。使用过时的多线程结构也使问题变得更加复杂。考虑一个更简单的例子:

static int F2(int n)
{
  if (n <= 1) return 1;

  var a = Task.Run(() => F1(n - 1));
  var b = Task.Run(() => F1(n - 2));

  return a.Result + b.Result;
}

我们将原始工作量(相当简单)分成两个分支。由于两个分支的工作负载大致相似,因此我们可以有效地使用两个线程。请注意,这是非常愚蠢 - 你计算两次相同的东西,并使用两个线程来完成单个线程可以做的工作量(即使没有更深入地了解递归函数,如斐波纳契序列)只是通过缓存给定n的结果。

但是我会假设练习的目的是展示如何将任务并行化,无论如何实现这些任务的并行化(即你期望)是愚蠢和天真)。我们是如何从双线程版本到四线程版本的?跳过第一次迭代,然后立即开始第二次迭代。这为您提供了四个分支,而不是原始分支。

假设您有n > 6(在示例中,F1被用作特殊情况)。 F1的第一次迭代大致为:

return F1(n - 1) + F1(n - 2);

但我们希望将其与第二次迭代合并,以允许四向并行化。这就像用F1(n)代替F1(n - 1) + F1(n - 2)

一样简单
return F1((n - 1) - 1) + F1((n - 1) - 2) + F1((n - 2) - 1) + F1((n - 2) - 2);

可以简化为

return F1(n - 2) + F1(n - 3) + F1(n - 3) + F1(n - 4);

进一步

return F1(n - 2) + 2 * F1(n - 3) + F1(n - 4);

糟糕!我们失去了一个分支机构。所以我们实际上需要另一个替换:

return 
  F1((n - 2) - 1) + F1((n - 2) - 2)
  + 2 * (F1((n - 3) - 1) + F1((n - 3) - 2))
  + F1((n - 4) - 1) + F1((n - 4) - 2);

哪个适用于......

return
 F1(n - 3) + F1(n - 4)
 + 2 * F1(n - 4) + 2 * F1(n - 5)
 + F1(n - 5) + F1(n - 6);

最终让我们进入了四个分支:

return
 F1(n - 3) + 3 * F1(n - 4) + 3 * F1(n - 5) + F1(n - 6);

每个分支都可以天真地并行运行,并且可以很好地利用所有四个线程。

您现在可以将相同的推理应用于G1并获得该特定递归函数的四向并行化。

答案 1 :(得分:0)

@Luaan这是我的解决方案,使用两个线程。我不知道它是否正常,但是顺序和并行算法的结果是匹配的,但是时间的减少极少,可能需要使用更多的线程?

private static int G2(int k, int r)
{
    if (k == 0 || r == 0) return 0;

    if (s[k - 1] > r) // this part gives the wrong results :(
    {
        Task<int> task1 = Task.Run(() => G1(k - 2, r));
        Task<int> task2 = Task.Run(() => G1(k - 3, r));
        return task1.Result + task2.Result;
    }

    Task<int> max1 = Task.Run(() => G1(k - 1, r));
    Task<int> max2 = Task.Run(() => p[k - 1] + G1(k - 1, r - s[k - 1]));

    return Math.Max(max1.Result, max2.Result);
}

如果(s [k - 1]&gt; r)块给出错误的结果:(