Reputation: 2461
Well, I'm using Parallel ForEach to process a very big array of Colors (35M of indexers).
I'm using Partitioner.Create to do this with perfomance. But something unexpected is happening:
private Color32[] MainGeneration(Color32[] proccesed, Color32[] dupe, int width, int height)
{
int i = 0;
Parallel.ForEach(Partitioner.Create(0, totalIndexes), item =>
{
int x = i % width;
int y = height - i / width - 1;
dupe[i] = (UnityEngine.Color)MapIteration((Color)((Color)(UnityEngine.Color)proccesed[i]).Clone(), i, x, y)
++i;
if (i % updateInterlockedEvery == 0)
{
++currentIndex; //Interlocked.Increment(ref currentIndex);
}
});
// If Parallel.ForEach is blocking, why this is outputting me 12 when total indexes value is 35.000.000?
Debug.Log(i);
return dupe;
}
As I wroted on the comment, why is this happening?
The expected behaviour of this, is to process a large image using parallelism, not only a small piece.
processed
contains the full image.
dupe
contains a empty array that will be completed on each iteration.
I do all this in the local scope to avoid heap problems.
Upvotes: 0
Views: 473
Reputation: 35746
Don't you want something like, Fiddle Here
using System.Collections.Concurrent;
using System.Threading.Tasks;
using UnityEngine;
public class Program
{
private void MainGeneration(
Color32[] source,
Color32[] target,
int width,
int height)
{
Parallel.ForEach(Partitioner.Create(source, true)
.GetOrderableDynamicPartitions(), colorItem =>
{
var i = colorItem.Key;
var color = colorItem.Value;
var x = i % width;
var y = height - i / width - 1;
target[i] = this.Map(color, i, x, y);
});
}
private Color32 Map(Color32 color, long i, long x, long y)
{
return color;
}
}
Upvotes: 2
Reputation: 1886
++i;
is in fact shorthand for something like this:
temp = i + 1;
i = temp;
Luckily you are using int not long, so at least the i = temp;
assignment is atomic, the explanation is easier :)
If two threads are both doing ++i; something like this can happen (only two threads considered for simplicity):
//Let's say i == 0
Thread 2 calculates i + 1 //== 1
Thread 1 calculates i + 1 //== 1
Thread 1 sets i = 1;
Thread 2 sets i = 1;
So here you would probably expect i to be 2, but in fact it is 1 by the end of this.
If you want to increment i in a threadsafe manner you can do:
Interlocked.Increment(ref i);
As pointed in your code for currentIndex which should be also calculated like this.
I see another issue with the code given the huge discrepancy in numbers. Exceptions that happen outside of the main thread are not reported to/on the main thread if the IsBackGround property of the thread is true. To guard against this, you should try/catch the inner block in the foreach to also count exceptions in a similar way.
Or even better, get a list of exceptions in a ConcurrentQueue/ConcurrentBag :
// Use ConcurrentQueue to enable safe enqueueing from multiple threads.
var exceptions = new ConcurrentQueue<Exception>();
// Execute the complete loop and capture all exceptions.
Parallel.ForEach(data, d =>
{
try
{
// Cause a few exceptions, but not too many.
if (d < 3)
throw new ArgumentException($"Value is {d}. Value must be greater than or equal to 3.");
else
Console.Write(d + " ");
}
// Store the exception and continue with the loop.
catch (Exception e)
{
exceptions.Enqueue(e);
}
});
Console.WriteLine();
// Throw the exceptions here after the loop completes.
if (exceptions.Count > 0) throw new AggregateException(exceptions);
Upvotes: 2