使用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
}
}
}
您编写PointGenerator
的方式几乎无法从并行执行中获益。
lock
意味着它将具有基本上单线程的性能,并具有额外的线程开销- 全局状态CCD_ 3意味着您必须同步访问。当然,由于它只是一个int,您可以使用
Interlocked
类来有效地递增它
我想让每个PointGenerator
都有一个不同的Random
对象和一个不同计数器。这样就不会有任何可能导致问题的共享可变状态。但是要小心,Random
的默认构造函数使用系统的刻度计数。创建多个对象可能会产生具有相同种子的随机生成器。
所有PointGenerator
完成后,您将合并结果。
这与Parallel.For
和Parallel.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
实例,而不是单个实例。