The Task Parallel Library Sampler – Part 6: Parallel.For Sample

Part One: Starting with MVVM
Part Two: The MVVM solution structure and basic framework
Part Three: Base Classes
Part 4: Sampler View, View Model and Model
Part 5: Running and working with the TPL samples

In the solution directory Models, you will find the LineSample and LineParallelSample models. These are fairly straight forward samples.

LineSample.Run()

public override void Run(System.Drawing.Bitmap bmp = null, Action<string> UpdateLog = null)
{
	if(bmp == null)
		throw new InvalidOperationException("Bitmap must be defined.");
	
	double X1 = 0, Y1 = 0;
	double X2 = bmp.Width - 1, Y2 = bmp.Height - 1;

	Stopwatch s = new Stopwatch();
	s.Start();

	double slope = (Y2 - Y1) / (X2 - X1);
	double beta = Y1 - (slope * X1);

	System.Drawing.Imaging.BitmapData bmData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
	int stride = bmData.Stride;
	System.IntPtr Scan0 = bmData.Scan0;
	unsafe
	{
		byte* startPos = (byte*)(void*)Scan0;
		for (int x = (int)X1; x <= X2; x++)
		{
			GeneralMathOperations.DrawPixelByPointSlope(slope, beta, stride, startPos, x);
		}
	}
	bmp.UnlockBits(bmData);

	s.Stop();
	RunTime = s.Elapsed;
}

We start a stop watch. Then calculate our slope and beta for the point slope formula. Since we want to use the image so that we can compare the same operation between the two samples we have to use some pointer operations that makes all this a lot easier. Then we run through a for loop that just moves from the top left to the bottom right and sets each pixel in a line to black. We then unlock the image, stop the stop watch and then set our RunTime to the time it took for the loop to run.

LineParallelSample.Run()

public override void Run(System.Drawing.Bitmap bmp = null, Action UpdateLog = null)
{
	if(bmp == null)
		throw new InvalidOperationException("Bitmap must be defined.");

	double X1 = 0, Y1 = 0;
	double X2 = bmp.Width - 1, Y2 = bmp.Height - 1;

	Stopwatch s = new Stopwatch();
	s.Start();

	double slope = (Y2 - Y1) / (X2 - X1);
	double beta = Y1 - (slope * X1);

	System.Drawing.Imaging.BitmapData bmData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
	int stride = bmData.Stride;
	System.IntPtr Scan0 = bmData.Scan0;
	unsafe
	{
		byte* startPos = (byte*)(void*)Scan0;
		Parallel.For((int)X1, (int)X2 + 1, x =>
		{
			GeneralMathOperations.DrawPixelByPointSlope(slope, beta, stride, startPos, x);
		});
	}
	bmp.UnlockBits(bmData);

	s.Stop();
	RunTime = s.Elapsed;
}

In LineParallelSample the only difference is the Parallel.For at line 21. Parallel.For’s first parameter is where to start, the second parameter is where to end and the third parameter is the body of the action. It’s important to remember that the “from” is inclusive meaning it includes the value and the “to” is exclusive meaning it goes to this amount doesn’t include it. That is why in LineSample we run to x <= X2 but in LineParallelSample we run to X2 + 1 which ends up being x < X2 + 1. We want to make sure we include that last pixel. What is really interesting is the results. Running the samples a few times with the included image (one of my son) you will see similar results such as these:

Reseting Image
Starting Line Sample
Completed Line Sample
Line Sample ran in 00:00:00.0009594

Reseting Image
Starting Parallel Line Sample
Completed Parallel Line Sample
Parallel Line Sample ran in 00:00:00.0009714

Changing to a much larger image you end up with similar results.
So, what is so exciting? Well, the Parallel.For doesn’t help. But, but… well, that can’t be right.
Ah, however, it is right. When using the Parallel.For and Parallel.ForEach you have to remember that there is a bit of overhead when managing the threads, spinning up the threads and context switching. I wrote this sample to explicitly show that the TPL isn’t a magic bullet. In order to maximize your use of the TPL you have to give each thread enough work. In this sample all it is doing is drawing a single point. This is pretty simple to do and the overhead of the threading doesn’t justify using the TPL in this instance.

In the next post we’ll go over the three grey scale samples where using a Parallel.For and Parallel.ForEach make a huge difference.

Thanks,
Brian

Leave a Reply