使用TPL加速pi的计算

本文关键字:计算 pi 加速 TPL 使用 | 更新日期: 2023-09-27 18:23:45

我需要使用任务并行库通过蒙特卡罗方法计算Pi数,但当我的并行程序运行时,它计算Pi的时间比它的非并行模拟要长得多。两个人怎么解决?并行计算类及其非并行模拟如下:

class CalcPiTPL
    {
        Object randLock = new object();
        int n;
        int N_0;
        double aPi;
        public StringBuilder Msg; // diagonstic message
        double x, y;
        Stopwatch stopWatch = new Stopwatch();
        public void Init(int aN)
        {
            stopWatch.Start();
            n = aN; // save total calculate-iterations amount
            aPi = -1; // flag, if no any calculate-iteration has been completed
            Msg = new StringBuilder("No any calculate-iteration has been completed");
        }
        public void Run()
        {
            if (n < 1)
            {
                Msg = new StringBuilder("Inbalid N-value");
                return;
            }
            Random rnd = new Random(); // to create randomizer
            Task[] tasks = new Task[4];
            tasks[0] = Task.Factory.StartNew(() => PointGenerator(n, rnd));
            tasks[1] = Task.Factory.StartNew(() => PointGenerator(n, rnd));
            tasks[2] = Task.Factory.StartNew(() => PointGenerator(n, rnd));
            tasks[3] = Task.Factory.StartNew(() => PointGenerator(n, rnd));
            Task.WaitAll(tasks[0], tasks[1], tasks[2], tasks[3]);
            aPi = 4.0 * ((double)N_0 / (double)n); // to calculate approximate Pi - value
            stopWatch.Stop();
            TimeSpan ts = stopWatch.Elapsed;
            string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
            ts.Hours, ts.Minutes, ts.Seconds,
            ts.Milliseconds / 10);
            Console.WriteLine("RunTime " + elapsedTime);
        }
        public double Done()
        {
            if (aPi > 0)
            {
                Msg = new StringBuilder("Calculates has been completed successful");
                return aPi; // return gotten value
            }
            else
            {
                return 0; // no result
            }
        }
        public void PointGenerator(int n, Random rnd)
        {
            for (int i = 1; i <= n / 4; i++)
            {
                lock (randLock)
                {
                    x = rnd.NextDouble(); // to generate coordinates
                    y = rnd.NextDouble(); // 
                    if (((x - 0.5) * (x - 0.5) + (y - 0.5) * (y - 0.5)) < 0.25)
                    {
                        //Interlocked.Increment(ref N_0); 
                        N_0++; // coordinate in a circle! mark it by incrementing N_0
                    }
                }
            }
        }
    }

无与伦比的模拟:

class TCalcPi//unparallel calculating method
    {
        int N;
        int N_0;
        double aPi;
        public StringBuilder Msg; // diagnostic message
        double x, y;
        Stopwatch stopWatch = new Stopwatch();
        public void Init(int aN)
        {
            stopWatch.Start();
            N = aN; // save total calculate-iterations amount
            aPi = -1; // flag, if no any calculate-iteration has been completed
            Msg = new StringBuilder("No any calculate-iteration has been completed");
        }
        public void Run()
        {
            if (N < 1)
            {
                Msg = new StringBuilder("Invalid N - value");
                return;
            }
            int i;
            Random rnd = new Random(); // to create randomizer
            for (i = 1; i <= N; i++)
            {
                x = rnd.NextDouble(); // to generate coordinates
                y = rnd.NextDouble(); // 
                if (((x -  0.5) * (x -  0.5) + (y -  0.5) * (y -  0.5)) <  0.25)
                {
                    N_0++; // coordinate in a circle! mark it by incrementing N_0
                }
            }
            aPi = 4.0 * ((double)N_0 / (double)N); // to calculate approximate Pi - value
            stopWatch.Stop();
            TimeSpan ts = stopWatch.Elapsed;
            string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
            ts.Hours, ts.Minutes, ts.Seconds,
            ts.Milliseconds / 10);
            Console.WriteLine("RunTime " + elapsedTime);
        }
        public double Done()
        {
            if (aPi > 0)
            {
                Msg = new StringBuilder("Calculates has been completed successful");
                return aPi; // return gotten value
            }
            else
            {
                return 0; // no result
            }
        }
    }

使用TPL加速pi的计算

您编写PointGenerator的方式几乎无法从并行执行中获益。

  • lock意味着它将具有基本上单线程的性能,并具有额外的线程开销
  • 全局状态CCD_ 3意味着您必须同步访问。当然,由于它只是一个int,您可以使用Interlocked类来有效地递增它

我想让每个PointGenerator都有一个不同的Random对象和一个不同计数器。这样就不会有任何可能导致问题的共享可变状态。但是要小心,Random的默认构造函数使用系统的刻度计数。创建多个对象可能会产生具有相同种子的随机生成器。

所有PointGenerator完成后,您将合并结果。

这与Parallel.ForParallel.ForEach的一些TPL过载非常相似。

我知道这篇文章很旧,但在C#中搜索如何并行计算pi时,它仍然会出现。我已经对此进行了修改,以便为工作人员使用系统线程数。此外,如果我们为worker使用返回类型,将一些其他变量放入worker函数中,并最终让另一个任务将所有内容组合在一起,则不需要锁。这将使用long来获得更大的迭代次数。Random的实例是以线程id为种子创建的,我希望这能使它们给出不同的随机数序列。删除了Init方法,而将初始化放在Run方法中。现在有两种使用方法,阻塞和非阻塞。但首先是类:

public class CalcPiTPL
{
    private long n;
    private double pi;
    private Stopwatch stopWatch = new Stopwatch();
    private Task<int>[]? tasks = null;
    private Task? taskOrchestrator = null;
    private ManualResetEvent rst = new ManualResetEvent(false);
    private bool isDone = false;
    public string elapsedTime = string.Empty;
    public double Pi { get { return pi; } }
    public void Run(long n)
    {
        if (n < 1 || taskOrchestrator!=null) return;
        isDone = false;
        rst.Reset();
        stopWatch.Start();
        this.n = n; // save total calculate-iterations amount
        pi = -1; // flag, if no any calculate-iteration has been completed
        tasks = new Task<int>[Environment.ProcessorCount];
        for(int i = 0; i < Environment.ProcessorCount; i++)
        {
            tasks[i] = Task.Factory.StartNew(() => PointGenerator(n));
        }
        taskOrchestrator = Task.Factory.StartNew(() => Orchestrator());
    }
    private void Orchestrator()
    {
        Task.WaitAll(tasks);
        
        long N_0 = 0;
        foreach (var task in tasks)
        {
            N_0 += task.GetAwaiter().GetResult();
        }
        pi = 4.0 * ((double)N_0 / (double)n); // to calculate approximate Pi - value
        stopWatch.Stop();
        TimeSpan ts = stopWatch.Elapsed;
        elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
        tasks = null;
        taskOrchestrator = null;
        isDone = true;
        rst.Set();
    }
    public double Wait()
    {
        rst.WaitOne();
        return pi;
    }
    public bool IsDone()
    {
        return isDone;
    }
    private int PointGenerator(long n)
    {
        int N_0 = 0;
        Random rnd = new Random(Thread.CurrentThread.ManagedThreadId);
        for (int i = 1; i <= n / Environment.ProcessorCount; i++)
        {
            double x = rnd.NextDouble(); // to generate coordinates
            double y = rnd.NextDouble(); // 
            if (((x - 0.5) * (x - 0.5) + (y - 0.5) * (y - 0.5)) < 0.25)
            {
                N_0++;
            }
        }
        
        return N_0;
    }
}

阻塞呼叫:

CalcPiTPL pi = new CalcPiTPL();
pi.Run(1000000000);
Console.WriteLine(pi.Wait());

非阻塞呼叫:

CalcPiTPL pi = new CalcPiTPL();
pi.Run(100000000);
while (pi.IsDone()==false)
{
    Thread.Sleep(100);
    // Do something else
}
Console.WriteLine(pi.Pi);

如果有人想在GUI应用程序中使用事件,那么添加一个事件可能会很好。也许我以后会那样做。如果我把事情搞砸了,请随意纠正。

当整个并行部分都在锁范围内时,实际上没有什么是并行的。在任何给定的时刻,只有一个线程可以在锁范围内。

您可以简单地使用不同的Random实例,而不是单个实例。