平行斐波纳契数计算器

时间:2011-10-06 09:38:38

标签: c# task-parallel-library fibonacci

我正在使用任务并行库(TPL)来计算Fibonacci数。 计划如下:

        public static int Fib(int n)
        {
            if (n <= 1)
            {
                return n;
            }
            Task<int> task = Task.Factory.StartNew<int>(() => Fib(n - 1));
            var p = Fib(n - 2);
            return task.Result + p;
        }

        public static void Main(string[] args)
        {

            Stopwatch watch = new Stopwatch();
            watch.Start();
            Console.WriteLine("Answer: " + Fib(44));
            watch.Stop();
            Console.WriteLine("Time: " + watch.ElapsedMilliseconds);
        }
    }

不幸的是,这个程序需要很长时间才能完成。 但是这个程序的串行版本(如下所示)只需不到30秒 计算第44个斐波纳契数。

 public class FibTester
    {
        public static int Fib(int n)
        {
            if (n <= 1)
            {
                return n;
            }
            var q = Fib(n - 1);
            var p = Fib(n - 2);
            return p + q;
        }

        public static void Main(string[] args)
        {

            Stopwatch watch = new Stopwatch();
            watch.Start();
            Console.WriteLine("Answer: " + Fib(44));
            watch.Stop();
            Console.WriteLine("Time: " + watch.ElapsedMilliseconds);
        }
    }

我认为并行版本的问题是,它为每个Fib(n - 1)创建一个帖子 请求。有没有办法控制在TPL中创建的线程数?

4 个答案:

答案 0 :(得分:7)

这是多线程的完美示例!

您正在为递归函数的每次迭代创建一个新任务。因此,每个任务都会创建一个新任务,等待该任务完成,然后添加结果中的数字。

每个线程有两个作业:1 - 创建新线程,2 - 添加两个数字。

创建每个线程的间接成本远远超过将两个数字加在一起的成本。


要回答有关限制创建的线程数的问题,TPL使用ThreadPool。您可以使用ThreadPool.SetMaxThreads限制线程数。

答案 1 :(得分:4)

我认为很明显斐波那契不能并行化,除非您提前了解一些相邻的斐波那契数字

只需去寻找迭代代码。

无论你做什么,都不要在每次迭代/递归时产生任务/线程!开销会破坏性能。即使适用并行化,这也是一个很大的反模式。

答案 2 :(得分:3)

只是为了好玩:)

using System;
using System.Linq;
using System.Threading.Tasks;

public class Program
{
    static readonly double sqrt5 = Math.Sqrt(5);
    static readonly double p1 = (1 + sqrt5) / 2;
    static readonly double p2 = -1 * (p1 - 1);

    static ulong Fib1(int n) // surprisingly slightly slower than Fib2
    {
        double n1 = Math.Pow(p1, n+1);
        double n2 = Math.Pow(p2, n+1);
        return (ulong)((n1-n2)/sqrt5);
    }

    static ulong Fib2(int n) // 40x faster than Fib3
    {
        double n1 = 1.0;
        double n2 = 1.0;
        for (int i=0; i<n+1; i++)
        {
            n1*=p1;
            n2*=p2;
        }
        return (ulong)((n1-n2)/sqrt5);
    }

    static ulong Fib3(int n) // that's fast! Done in 1.32s
    {
        double n1 = 1.0;
        double n2 = 1.0;
        Parallel.For(0,n+1,(x)=> {
            n1 *= p1; 
            n2 *= p2; 
        });
        return (ulong)((n1-n2)/sqrt5);
    }

    public static void Main(string[] args)
    {
        for (int j=0; j<100000; j++)
            for (int i=0; i<90; i++)
                Fib1(i);
        for (int i=0; i<90; i++)
            Console.WriteLine(Fib1(i));
    }
}

答案 3 :(得分:2)

你的程序非常有用,因为重复相同的计算(Fib(n-1)实际上重新计算所有数字的Fib数

你应该试试这个:

class Program
{
    static void Main(string[] args)
    {
        var sw = new Stopwatch();
        sw.Start();
        foreach (var nbr in Fibo().Take(5000))
        {
            Console.Write(nbr.ToString() + " ");
        }
        sw.Stop();
        Console.WriteLine();
        Console.WriteLine("Ellapsed : " + sw.Elapsed.ToString());
        Console.ReadLine();
    }

    static IEnumerable<long> Fibo()
    {
        long a = 0;
        long b = 1;
        long t;

        while (true)
        {
            t = a + b;
            yield return t;
            a = b;
            b = t;
        }
    }
}

第44次在5ms内找到。

代码中最慢的部分是循环中的Console.Write。