【问题标题】:Parallel Fibonacci Number Calculator平行斐波那契数计算器
【发布时间】:2011-10-06 09:38:37
【问题描述】:

我正在使用任务并行库 (TPL) 来计算斐波那契数。 程序如下:

        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 中创建的线程数?

【问题讨论】:

  • 您是否考虑过使用迭代方法?
  • 我认为很明显斐波那契不能并行化,除非你提前知道一些相邻的斐波那契数对。
  • 是的,一般我想知道如何限制TPL创建的线程数。
  • 为什么要使用指数运行时算法,然后向其抛出多线程,而不是使用简单的快速算法?
  • 我相信问题本身是超线性的。所以无论如何你都会得到一个序列。

标签: c# task-parallel-library fibonacci


【解决方案1】:

这是多线程的完美示例!

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

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

创建每个线程的开销成本将远远超过将两个数字相加的成本。


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

【讨论】:

  • 更重要的是:每个任务直接取决于刚刚启动的线程的结果。因此:没有并行化。只有模糊和性能开销
  • @sehe,没有比这更糟糕的了。对 Fib(n-2) 的递归调用是在主线程上进行的,我们从 Fib(n) 到 Fib(n-2) 直到创建 n
  • 对。我正是这个意思。有很多模糊,但没有算法并行化
【解决方案2】:

我认为很明显斐波那契不能并行化,除非你提前知道一些相邻的斐波那契数对

只需要迭代代码。

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

【讨论】:

  • +1 同意,一个简单的 for 循环应该是最快的。
【解决方案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));
    }
}

【讨论】:

  • @sehe 你太认真了:)
  • 为什么,这是一个我不知道的好方法。我想知道它是否还能发挥作用。我还认为看到这个方法在并行模式下仍然是“有效”很有趣(假设在.NET中原子加载/存储双打)
【解决方案4】:

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

你应该试试这个:

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;
        }
    }
}

在 5 毫秒内找到第 44 个。

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

【讨论】:

  • 你的算法错了,你需要a变成b,b变成你返回的结果。例如var nextNumber = a + b; a = b; b = nextNumber; yield return nextNumber;
猜你喜欢
  • 1970-01-01
  • 2013-03-31
  • 2020-01-18
  • 1970-01-01
  • 2017-07-22
  • 2017-11-18
  • 2016-12-11
  • 2020-04-02
  • 2013-08-12
相关资源
最近更新 更多