Last active
December 30, 2017 20:41
-
-
Save gfoidl/d8250ff11a5c70972cb8164622792ba1 to your computer and use it in GitHub Desktop.
Custom Partitioner for Parallel.ForEach and PLinq
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//#define ORDER | |
//----------------------------------------------------------------------------- | |
using System; | |
using System.Collections; | |
using System.Collections.Concurrent; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace ConsoleApp1 | |
{ | |
static class Program | |
{ | |
static void Main() | |
{ | |
var arr = new int[21]; | |
int arrayLength = arr.Length; | |
var myPartitioner = new MyPartitioner(arrayLength); | |
Console.WriteLine("Parallel.ForEach"); | |
Parallel.ForEach( | |
myPartitioner, | |
range => Console.WriteLine(range) | |
); | |
Console.WriteLine(); | |
Console.WriteLine("PLinq"); | |
myPartitioner | |
.AsParallel() | |
#if ORDER | |
.OrderBy(a => a.Start) | |
.ToList() | |
.ForEach(a => Console.WriteLine(a)); | |
#else | |
.ForAll(a => Console.WriteLine(a)); | |
#endif | |
} | |
} | |
//------------------------------------------------------------------------- | |
internal class MyPartitioner : OrderablePartitioner<(int Start, int End)> | |
{ | |
private int _arrayLength; | |
//--------------------------------------------------------------------- | |
public MyPartitioner(int arrayLength) | |
: base(true, true, true) | |
=> _arrayLength = arrayLength; | |
//--------------------------------------------------------------------- | |
public override bool SupportsDynamicPartitions => true; | |
//--------------------------------------------------------------------- | |
// For Parallel.ForEach this method is not needed, only for PLinq | |
public override IList<IEnumerator<KeyValuePair<long, (int Start, int End)>>> GetOrderablePartitions(int partitionCount) | |
{ | |
var dynamicPartitions = this.GetOrderableDynamicPartitions(); | |
var partitions = new IEnumerator<KeyValuePair<long, (int Start, int End)>>[partitionCount]; | |
for (int i = 0; i < partitions.Length; ++i) | |
partitions[i] = dynamicPartitions.GetEnumerator(); | |
return partitions; | |
} | |
//--------------------------------------------------------------------- | |
public override IEnumerable<KeyValuePair<long, (int Start, int End)>> GetOrderableDynamicPartitions() | |
=> new ArrayRangePartitions(_arrayLength); | |
//--------------------------------------------------------------------- | |
// Could be implemented as enumerator in GetOrderableDynamicPartitions | |
// and with the fields of the class. | |
// But then only one "iteration" Parallel.ForEach or PLinq can be done. | |
// With the separate class multiple iterations can be done. | |
private class ArrayRangePartitions : IEnumerable<KeyValuePair<long, (int Start, int End)>> | |
{ | |
private readonly int _arrayLen; | |
private readonly int _chunkSize; | |
private readonly int _noOfChunks; | |
private int _chunkIdx; | |
//----------------------------------------------------------------- | |
public ArrayRangePartitions(int arrayLen) | |
{ | |
int chunkSize = arrayLen / Environment.ProcessorCount; | |
_noOfChunks = arrayLen / chunkSize; | |
_arrayLen = arrayLen; | |
_chunkSize = chunkSize; | |
} | |
//----------------------------------------------------------------- | |
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); | |
//----------------------------------------------------------------- | |
public IEnumerator<KeyValuePair<long, (int Start, int End)>> GetEnumerator() | |
{ | |
while (true) | |
{ | |
int chunkIdx = Interlocked.Increment(ref _chunkIdx) - 1; | |
if (chunkIdx > _noOfChunks) yield break; | |
if (chunkIdx < _noOfChunks) | |
{ | |
int start = chunkIdx * _chunkSize; | |
int end = start + _chunkSize; | |
yield return new KeyValuePair<long, (int Start, int End)>(chunkIdx, (start, end)); | |
} | |
else | |
{ | |
int start = chunkIdx * _chunkSize; | |
int end = _arrayLen; | |
yield return new KeyValuePair<long, (int Start, int End)>(chunkIdx, (start, end)); | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment