问题
how do I convert this sequential recursive algorithm into a parallel recursive algorithm using tasks?
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]));
}
I have an example, but it's too confusing, it has no explanations and is not as complex as in my case.
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;
}
回答1:
The overhead from parallelization is rather high for a simple task like this, so you definitely don't want to parallelize every iteration naïvely. However, the sample task is rather complicated in not only changing the recursive function to a (somewhat) parallelized recursive function, but finding extra parallelization options to get four independent workers. It's also further complicated by using outdated multi-threading constructs. Consider a simpler example:
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;
}
We split the original workload (rather trivially) into two branches. Since both branches have roughly similar amount of workload, this allows us to efficiently use two threads. Note that this is extremely stupid - you're computing the same thing twice, and using two threads to do the workload that a single thread could do just as well (even without a deeper understanding of recursive functions like the Fibonacci sequence) just by caching results for a given n.
But I'll assume that the point of the excercise is to show how you can parallelize tasks regardless of how parallelizable those tasks actually are (i.e. you're expected to be stupid and naïve). How did we get from the two-threaded version to the four-threaded version? By skipping the first iteration, and starting on the second right away. This gives you four branches, instead of the original two.
Let's assume you have n > 6 (in the sample, F1 is used otherwise as a special case). The first iteration of F1 works out roughly to:
return F1(n - 1) + F1(n - 2);
But we want to merge this with the second iterations, to allow four-way parallelization. This is as simple as substituting F1(n) for F1(n - 1) + F1(n - 2):
return F1((n - 1) - 1) + F1((n - 1) - 2) + F1((n - 2) - 1) + F1((n - 2) - 2);
Which can be simplified to
return F1(n - 2) + F1(n - 3) + F1(n - 3) + F1(n - 4);
and further to
return F1(n - 2) + 2 * F1(n - 3) + F1(n - 4);
Oops! We lost one of the branches. So we actually need another substitution:
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);
Which works out to...
return
F1(n - 3) + F1(n - 4)
+ 2 * F1(n - 4) + 2 * F1(n - 5)
+ F1(n - 5) + F1(n - 6);
Which finally gets us to our four branches:
return
F1(n - 3) + 3 * F1(n - 4) + 3 * F1(n - 5) + F1(n - 6);
Each of those branches can naïvely be run in parallel, and you get a good use of all four threads.
You can now hopefully apply the same reasoning to G1 and get four-way parallelization of that particular recursive function :)
回答2:
@Luaan this is my solution, using two threads. I don't know if it's alright, but the results of sequential and parallel algorithms match, however there's extremely little decrease in time, perhaps more threads need to be used?
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);
}
if (s[k - 1] > r) block gives the wrong results :(
来源:https://stackoverflow.com/questions/44135281/compute-a-recursive-algorithm-in-parallel-using-task