为什么并行多线程代码执行比顺序慢?
c#
我想使用多个线程使用矩形和梯形方法执行积分计算以获得更快的结果。不幸的是,就我而言,执行多线程代码比标准顺序代码慢。使用多线程比单线程慢得多——毕竟,不应该反过来吗?感觉线程越多,代码执行得越慢。
另外,我注意到线程越多,积分的结果就越不精确。这在使用梯形法计算积分时尤为明显。
这是我的代码:https :
//dotnetfiddle.net/jEPURO
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace ParallelProgramming.ConsoleApp
{
class Program
{
public static string IntegrationMethod { get; set; }
public static double IntervalBegin { get; set; }
public static double IntervalEnd { get; set; }
public static int NPrecisionValue { get; set; }
public static bool IsParallel { get; set; }
public static int ThreadValue { get; set; }
public static Stopwatch Stopwatch { get; set; }
public static double Result { get; set; }
static void Main(string[] args)
{
Console.WriteLine("Function | Elapsed Time | Estimated Integral");
Console.WriteLine("-----------------------------------------------------------------");
IntervalBegin = 5;
IntervalEnd = -2;
NPrecisionValue = 100000000;
//RectangularIntegration – Sequential
NumericalIntegrationMethods integral = new();
Stopwatch = Stopwatch.StartNew();
Result = integral.RectangularIntegration(IntervalBegin, IntervalEnd, NPrecisionValue);
Stopwatch.Stop();
Console.WriteLine($"{nameof(integral.RectangularIntegration)} – Sequential | {Stopwatch.Elapsed} | {Result}");
//RectangularIntegrationParallel - 1 thread
integral = new();
Stopwatch = Stopwatch.StartNew();
Result = integral.RectangularIntegrationParallel(IntervalBegin, IntervalEnd, NPrecisionValue, 1);
Stopwatch.Stop();
Console.WriteLine($"{nameof(integral.RectangularIntegrationParallel)} – 1 Thread | {Stopwatch.Elapsed} | {Result}");
//RectangularIntegrationParallel - 2 threads
integral = new();
Stopwatch = Stopwatch.StartNew();
Result = integral.RectangularIntegrationParallel(IntervalBegin, IntervalEnd, NPrecisionValue, 2);
Stopwatch.Stop();
Console.WriteLine($"{nameof(integral.RectangularIntegrationParallel)} – 2 Threads | {Stopwatch.Elapsed} | {Result}");
//RectangularIntegrationParallel - 3 threads
integral = new();
Stopwatch = Stopwatch.StartNew();
Result = integral.RectangularIntegrationParallel(IntervalBegin, IntervalEnd, NPrecisionValue, 3);
Stopwatch.Stop();
Console.WriteLine($"{nameof(integral.RectangularIntegrationParallel)} – 3 Threads | {Stopwatch.Elapsed} | {Result}");
//RectangularIntegrationParallel - 4 threads
integral = new();
Stopwatch = Stopwatch.StartNew();
Result = integral.RectangularIntegrationParallel(IntervalBegin, IntervalEnd, NPrecisionValue, 4);
Stopwatch.Stop();
Console.WriteLine($"{nameof(integral.RectangularIntegrationParallel)} – 4 Threads | {Stopwatch.Elapsed} | {Result}");
//TrapezoidalIntegration - Sequential
integral = new();
Stopwatch = Stopwatch.StartNew();
Result = integral.TrapezoidalIntegration(IntervalBegin, IntervalEnd, NPrecisionValue);
Stopwatch.Stop();
Console.WriteLine($"{nameof(integral.TrapezoidalIntegration)} – Sequential | {Stopwatch.Elapsed} | {Result}");
//TrapezoidalIntegrationParallel – 1 Thread
integral = new();
Stopwatch = Stopwatch.StartNew();
Result = integral.TrapezoidalIntegrationParallel(IntervalBegin, IntervalEnd, NPrecisionValue, 1);
Stopwatch.Stop();
Console.WriteLine($"{nameof(integral.TrapezoidalIntegrationParallel)} – 1 Thread | {Stopwatch.Elapsed} | {Result}");
//TrapezoidalIntegrationParallel – 2 Threads
integral = new();
Stopwatch = Stopwatch.StartNew();
Result = integral.TrapezoidalIntegrationParallel(IntervalBegin, IntervalEnd, NPrecisionValue, 2);
Stopwatch.Stop();
Console.WriteLine($"{nameof(integral.TrapezoidalIntegrationParallel)} – 2 Threads | {Stopwatch.Elapsed} | {Result}");
//TrapezoidalIntegrationParallel – 3 Threads
integral = new();
Stopwatch = Stopwatch.StartNew();
Result = integral.TrapezoidalIntegrationParallel(IntervalBegin, IntervalEnd, NPrecisionValue, 3);
Stopwatch.Stop();
Console.WriteLine($"{nameof(integral.TrapezoidalIntegrationParallel)} – 3 Threads | {Stopwatch.Elapsed} | {Result}");
//TrapezoidalIntegrationParallel – 4 Threads
integral = new();
Stopwatch = Stopwatch.StartNew();
Result = integral.TrapezoidalIntegrationParallel(IntervalBegin, IntervalEnd, NPrecisionValue, 4);
Stopwatch.Stop();
Console.WriteLine($"{nameof(integral.TrapezoidalIntegrationParallel)} – 4 Threads | {Stopwatch.Elapsed} | {Result}");
Console.WriteLine("Press any key to continue...");
Console.ReadLine();
}
}
public class NumericalIntegrationMethods
{
double Function(double x)
{
return x * x + 2 * x;
}
public double RectangularIntegration(double xp, double xk, int n)
{
double dx, integral = 0;
dx = (xk - xp) / n;
for (int i = 1; i <= n; i++)
{
integral += dx * Function(xp + i * dx);
//Console.WriteLine("Sekwencyjnie - iteracja {0} w?tek ID: {1}", i, Thread.CurrentThread.ManagedThreadId);
}
return integral;
}
public double TrapezoidalIntegration(double xp, double xk, int n)
{
double dx, integral = 0;
dx = (xk - xp) / n;
for (int i = 1; i <= n; i++)
{
integral += Function(xp + i * dx);
//Console.WriteLine("Sekwencyjnie - iteracja {0} w?tek ID: {1}", i, Thread.CurrentThread.ManagedThreadId);
}
integral += (Function(xp) + Function(xk)) / 2;
integral *= dx;
return integral;
}
public double RectangularIntegrationParallel(double xp, double xk, int n, int maxThreads)
{
double dx, integral = 0;
dx = (xk - xp) / n;
Parallel.For(1, n + 1, new ParallelOptions { MaxDegreeOfParallelism = maxThreads }, i =>
{
integral += dx * Function(xp + i * dx);
//Console.WriteLine("Równolegle - iteracja {0} w?tek ID: {1}", i, Thread.CurrentThread.ManagedThreadId);
});
return integral;
}
public double TrapezoidalIntegrationParallel(double xp, double xk, int n, int maxThreads)
{
double dx, integral = 0;
dx = (xk - xp) / n;
Parallel.For(1, n + 1, new ParallelOptions { MaxDegreeOfParallelism = maxThreads }, i =>
{
integral += Function(xp + i * dx);
//Console.WriteLine("Równolegle - iteracja {0} w?tek ID: {1}", i, Thread.CurrentThread.ManagedThreadId);
});
integral += (Function(xp) + Function(xk)) / 2;
integral *= dx;
return integral;
}
}
}
这是输出:
Function | Elapsed Time | Estimated Integral
-----------------------------------------------------------------
RectangularIntegration – Sequential | 00:00:00.9284260 | -65.33333210831276
RectangularIntegrationParallel – 1 Thread | 00:00:01.7040507 | -65.33333210831276
RectangularIntegrationParallel – 2 Threads | 00:00:01.7191484 | -65.33333210831276
RectangularIntegrationParallel – 3 Threads | 00:00:01.6888398 | -57.73164823448317
RectangularIntegrationParallel – 4 Threads | 00:00:01.5530828 | -65.33333210831276
TrapezoidalIntegration – Sequential | 00:00:00.7278303 | -65.33333333332568
TrapezoidalIntegrationParallel – 1 Thread | 00:00:01.4265208 | -65.33333333332568
TrapezoidalIntegrationParallel – 2 Threads | 00:00:02.3009881 | -33.110522448239216
TrapezoidalIntegrationParallel – 3 Threads | 00:00:01.6062253 | -57.02137898750542
TrapezoidalIntegrationParallel – 4 Threads | 00:00:01.9967140 | -18.120285251376426
为什么会这样?我究竟做错了什么?毕竟,使用的线程越多,结果应该越快。4个线程应该比3个快,3个线程应该比2个快,依此类推。如何使用更多线程获得更快的结果?
回答
这是一种使用线程本地状态(accumulator参数)以允许每个线程独立工作的方式并行化计算的方法,与其他线程的干扰最小。一般来说,每个线程越少相互干扰,并行代码的效率就越高。
public double RectangularIntegrationParallel(double xp, double xk, int n, int maxThreads)
{
double dx, integral = 0;
dx = (xk - xp) / n;
var locker = new object();
Parallel.ForEach(Partitioner.Create(0, n + 1), new ParallelOptions
{
MaxDegreeOfParallelism = maxThreads
}, () => 0.0D, (range, state, accumulator) =>
{
for (int i = range.Item1; i < range.Item2; i++)
{
accumulator += dx * Function(xp + i * dx);
}
return accumulator;
}, accumulator =>
{
lock (locker) { integral += accumulator; }
});
return integral;
}
使用该Partitioner.Create方法的目的是将工作负载分块。您可以使用 将计算Partitioner的总范围拆分为子范围,并为每个子范围调用一次 lambda,而不是为计算的每个小循环调用 lambda。Lambda 调用不能由编译器内联,因此通常您希望避免每秒调用 lambda 数百万次。
Parallel.ForEach本示例中使用的重载具有以下签名:
public static ParallelLoopResult ForEach<TSource, TLocal>(
Partitioner<TSource> source,
ParallelOptions parallelOptions,
Func<TLocal> localInit,
Func<TSource, ParallelLoopState, TLocal, TLocal> body,
Action<TLocal> localFinally);
Parallel该类的替代方法是使用PLINQ。一般来说,PLINQ 生成更简洁、更易于理解的代码,通常以一些额外的开销为代价。
public double RectangularIntegrationParallel(double xp, double xk, int n, int maxThreads)
{
double dx = (xk - xp) / n;
return Partitioner.Create(0, n + 1)
.AsParallel()
.WithDegreeOfParallelism(maxThreads)
.Select(range =>
{
double integral = 0.0;
for (int i = range.Item1; i < range.Item2; i++)
{
integral += dx * Function(xp + i * dx);
}
return integral;
})
.Sum();
}