Skip to content

Instantly share code, notes, and snippets.

@gfoidl
Last active December 30, 2017 20:41
Show Gist options
  • Save gfoidl/d8250ff11a5c70972cb8164622792ba1 to your computer and use it in GitHub Desktop.
Save gfoidl/d8250ff11a5c70972cb8164622792ba1 to your computer and use it in GitHub Desktop.
Custom Partitioner for Parallel.ForEach and PLinq
//#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