本章继续学习实现数据并行,本文主要介绍任务并行度和自定义分区策略相关内容。
本教程对应学习工程:魔术师Dix / HandsOnParallelProgramming · GitCode
2、任务并行度
数据并行设计在系统的多个内核上以并行方式运行循环所带来的的优势,可以有效利用可用的 CPU 资源。我们可以使用另一个重要概念来控制循环中创建多少个任务:即 并行度(Degress of Parallelism)。
并行度(Degress of Parallelism)
整数,指定并行循环可以创建的最大任务数。默认值为64 。
下面用示例来演示:
private void RunByMaxDegreedOfParallelism()
{
int length = 10;
var L = TestFunction.GetTestList(length);
var options = new ParallelOptions { MaxDegreeOfParallelism = 2 };
var result = Parallel.For(0, length, options, (i, state) =>
{
Debug.Log($"Log [{i}] => {L[i]} : In Task : {Task.CurrentId}");
});
Debug.Log($"ParallelFor Result : {result.IsCompleted} | {result.LowestBreakIteration}");
}
当我们把最大并行度设置为2时,打印结果如下:
可见分成了2个任务执行,且非常有规律:Task 1 打印 0 ~ 4,Task 2 打印 5 ~ 9。同一个任务内都是顺序执行的,明显看得出来是分了2个任务。如果把最大并行度设置为1,结果如下:
看得出来在一个 Task 里顺序执行了。
对于某些高级应用场景中,正在运行的算法不能跨越一定数量的处理器,需要限制每个算法只使用一定数量的处理器,就应修改此设置。
3、在并行循环中创建自定义分区策略
分区(Partitionning)是数据并行中的另一个重要概念。为了在源集合中实现并行性,需要将其划分为较小的部分,成为范围(Range)或块(Chunk),这些部分可以由不同的线程同时访问。如果不进行分区,则循环将以串行方式执行。
分区程序可以分为以下两类(也可以创建自定义分区程序):
-
范围分区
-
块分区
PLINQ 和 TPL 的自定义分区程序 | Microsoft Learn详细了解:PLINQ 和 TPL 的自定义分区程序https://learn.microsoft.com/zh-cn/dotnet/standard/parallel-programming/custom-partitioners-for-plinq-and-tpl
3.1、范围分区
-
主要适用于事先已知长度的集合。
-
每个线程都有一个要处理的元素范围或源集合的开始和结束索引。
-
创建范围时会损失一些性能,但没有同步开销。
如何:加快小型循环体的速度 | Microsoft Learn了解详情:如何:加速小循环体https://learn.microsoft.com/zh-cn/dotnet/standard/parallel-programming/how-to-speed-up-small-loop-bodies?source=recommendations
3.2、块分区
使用块分区时,每个线程都会拾取一个元素块,对其进行处理,然后返回以拾取另一个尚未被其他线程拾取的元素。
-
主要用于链表(LinkedList)一类的集合,这些长度事先未知。
-
可提供更好的负载平衡功能,以防集合不均衡。
-
块大小取决于分区程序的实现,且存在同步开销(需要确保分配给两个线程的块不包含重复项)。
/// <summary>
/// 使用块分区进行并行
/// </summary>
private void RunByChunkPartitioning()
{
int length = 10;
OrderablePartitioner<Tuple<int, int>> orderablePartitioner = Partitioner.Create(0, length);
var result = Parallel.ForEach(orderablePartitioner, (range, state) =>
{
var startIndex = range.Item1;
var endIndex = range.Item2;
Debug.Log($"{Task.CurrentId} 分区:{startIndex} ~ {endIndex}");
});
Debug.Log($"Parallel.ForEach Result : {result.IsCompleted} | {result.LowestBreakIteration}");
}
分区结果如下:
Partitioner.Create 的介绍参考以下文档,他会自动创建一个分区程序,传入分区下限和上限。
Partitioner.Create 方法 (System.Collections.Concurrent) | Microsoft Learn创建分区程序。 https://learn.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.partitioner.create?view=netstandard-2.1
(未完待续)
本教程对应学习工程:魔术师Dix / HandsOnParallelProgramming · GitCode