I am currently writing a C# application to demonstrate the speedup of parallel computing over single threaded applications. My case is Median blur of an image. But putting more threads to work slows down the application significantly (60 seconds single threaded vs 75 seconds multithreaded) . Given my current approach, I don't know how I could improve the process for multithreading. Sorry in advance for the long code in this post.
my current approach:
first, I calculate how many pixels each thread needs to process to even out the work, the DateTime calculation is to know how much time is passed single threaded and how much time is passed multithreaded:
public void blurImage(int cores)
{
_startTotal = DateTime.Now;
int numberOfPixels = _originalImage.Width * _originalImage.Height;
if (cores>=numberOfPixels)
{
for (int i = 0; i < numberOfPixels; i++)
{
startThread(0, numberOfPixels);
}
}
else
{
int pixelsPerThread = numberOfPixels / cores;
int threshold = numberOfPixels - (pixelsPerThread * cores);
startThread(0, pixelsPerThread + threshold);
for (int i = 1; i < cores; i++)
{
int startPixel = i * pixelsPerThread + threshold;
startThread(startPixel, startPixel + pixelsPerThread);
}
}
_SeqTime = DateTime.Now.Subtract(_startTotal);
}
the startThread method starts a thread and saves the result into a special class object so it can all be merged into one image, I pass a copy of the input image in each thread.
private void startThread(int startPixel, int numberOfPixels)
{
BlurOperation operation = new BlurOperation(blurPixels);
_operations.Add(operation);
BlurResult result = new BlurResult();
operation.BeginInvoke((Bitmap)_processedImage.Clone(), startPixel, numberOfPixels, _windowSize, result, operation, new AsyncCallback(finish), result);
}
Every thread blurs their set of pixels and saves the result into a new list of colors, the result is saved into the result object as well as the startpixel and the current operation, so the program knows when all threads are finished:
private void blurPixels(Bitmap bitmap, int startPixel, int endPixel, int window, BlurResult result, BlurOperation operation)
{
List<Color> colors = new List<Color>();
for (int i = startPixel; i < endPixel; i++)
{
int x = i % bitmap.Width;
int y = i / bitmap.Width;
colors.Add(PixelBlurrer.ShadePixel(x, y, bitmap, window));
}
result._pixels = colors;
result._startPixel = startPixel;
result._operation = operation;
}
the PixelBlurrer calculates the median of each color channel and returns it:
public static Color ShadePixel(int x, int y, Bitmap image, int window)
{
List<byte> red = new List<byte>();
List<byte> green = new List<byte>();
List<byte> blue = new List<byte>();
int xBegin = Math.Max(x - window, 0);
int yBegin = Math.Max(y - window, 0);
int xEnd = Math.Min(x + window, image.Width - 1);
int yEnd = Math.Min(y + window, image.Height - 1);
for (int tx = xBegin; tx < xEnd; tx++)
{
for (int ty = yBegin; ty < yEnd; ty++)
{
Color c = image.GetPixel(tx, ty);
red.Add(c.R);
green.Add(c.G);
blue.Add(c.B);
}
}
red.Sort();
green.Sort();
blue.Sort();
Color output = Color.FromArgb(red[red.Count / 2], green[green.Count / 2], blue[blue.Count / 2]);
return output;
}
on the callback, we return to the GUI thread and merge all pixels into the resulting image. Lastly an event is called telling my form the process is done:
private void finish(IAsyncResult iar)
{
Application.Current.Dispatcher.BeginInvoke(new AsyncCallback(update), iar);
}
private void update(IAsyncResult iar)
{
BlurResult result = (BlurResult)iar.AsyncState;
updateImage(result._pixels, result._startPixel, result._operation);
}
private void updateImage(List<Color> colors, int startPixel, BlurOperation operation)
{
DateTime updateTime = DateTime.Now;
_operations.Remove(operation);
int end = startPixel + colors.Count;
for (int i = startPixel; i < end; i++)
{
int x = i % _processedImage.Width;
int y = i / _processedImage.Width;
_processedImage.SetPixel(x, y, colors[i - startPixel]);
}
if (_operations.Count==0)
{
done(this, null);
}
_SeqTime += DateTime.Now.Subtract(updateTime);
}
Any thoughts? I tried using Parallel.For instead of delegates, but that made it worse. Is there a way to speedup Median blur by multithreading or is this a failed case?