Reputation: 73
I'm trying to use parallel processing to speed up a couple of nested loops, but I'm having trouble getting the syntax right. I'm trying to get a count of how many pixels in a bitmap are either red, white, or black, the values for which I have in an enum elsewhere.
In serial processing I have the following code, which works fine:
Bitmap image = new Bitmap(@"Input.png");
var colourCount = new int[3];
for (var x = 0; x < image.Width; x++)
{
for (var y = 0; y < image.Height; y++)
{
switch (image.GetPixel(x, y).ToArgb())
{
case (int)colours.red: colourCount[0]++; break;
case (int)colours.white: colourCount[1]++; break;
case (int)colours.black: colourCount[2]++; break;
default: throw new ArgumentOutOfRangeException(string.Format("Unexpected colour found: '{0}'", image.GetPixel(x, y).ToArgb()));
}
}
}
I've seen code for parallel for loops by Microsoft and from Stackoverflow that update a shared variable such as below:
Parallel.For<int>(0, result.Count, () => 0, (i, loop, subtotal) =>
{
subtotal += result[i];
return subtotal;
},
(x) => Interlocked.Add(ref sum, x)
);
But all the examples use a simple type such as an int as the shared variable and I just can't figure out the syntax to write to my size three array. Am I approaching this all wrong?
By the way, I know in terms of performance that GetPixel is very slow compared to something like Bitmap.LockBits, I'm just trying to get the principle of parallel loops right.
Upvotes: 7
Views: 10472
Reputation: 54917
You can use an overload of Parallel.For
that permits you to maintain thread-local state. In this case, we create an int[3]
array for each thread that is spawned. Within each iteration of the parallel loop, we only update the local array, localColourCount
. Finally, when the thread is to be retired, we aggregate the results of each local array into the global one, colourCount
; however, since this is a shared data structure, we enforce mutual exclusion whilst accessing it.
Bitmap image = new Bitmap(@"Input.png");
var colourCount = new int[3];
Parallel.For(0, image.Width,
// localInit: The function delegate that returns the initial state
// of the local data for each task.
() => new int[3],
// body: The delegate that is invoked once per iteration.
(int x, ParallelLoopState state, int[] localColourCount) =>
{
for (var y = 0; y < image.Height; y++)
{
switch (image.GetPixel(x, y).ToArgb())
{
case (int)colours.red: localColourCount[0]++; break;
case (int)colours.white: localColourCount[1]++; break;
case (int)colours.black: localColourCount[2]++; break;
default: throw new ArgumentOutOfRangeException(
string.Format("Unexpected colour found: '{0}'",
image.GetPixel(x, y).ToArgb()));
}
}
},
// localFinally: The delegate that performs a final action
// on the local state of each task.
(int[] localColourCount) =>
{
// Accessing shared variable; synchronize access.
lock (colourCount)
{
for (int i = 0; i < 3; ++i)
colourCount[i] += localColourCount[i];
}
});
This code assumes that Bitmap.GetPixel
is thread-safe, which may or may not be the case.
Another thing you need to watch out for is that any ArgumentOutOfRangeException
instances will get combined into an AggregateException
, so you'll need to adjust your error-handling code.
Upvotes: 6