Making "Color to Gray" conversion algorithm parallel

Posted on in Blogs
One of the most important new features in RAD Studio XE7 is new "Parallel Programming Library". It is available for both Object Pascal and C++, and for both VCL and FireMonkey, multi-device projects.

During the recent CodeRage 9 virtual conference there were two very informative sessions on parallel programming from Allen Bauer and Danny Wind.

A few days ago I was doing VCL application modernisation workshop in Utrecht and thought it would be good to take an existing VCL application and see what it takes to make it parallel.

A few years ago I was playing with different ways of accessing individual pixels in the VCL bitmap on the example of "Color to Gray" algorithm. Clearly using bitmap's scanline is a few levels of magnitude faster than access through "Pixels" property. The differences in speed are discussed here: http://blogs.embarcadero.com/pawelglowacki/2010/04/15/39051

In the "color to gray" algorithm the color of every pixel is calculated independently from each other is a nested "for" loop. This is a perfect case where we can benefit from refactoring the code to use a parallel loop instead.

Let's consider a simple example of an existing code that we would like to convert:
``````procedure ToGrayPixelsNoParallel(aBitmap: Graphics.TBitmap; cg: TColor2Grayscale = c2gLuminosity);
var w, h: integer;
begin
if aBitmap <> nil then
for h := 0 to aBitmap.Height - 1 do
for w := 0 to aBitmap.Width - 1 do
aBitmap.Canvas.Pixels[w,h] :=
ColorToGray(aBitmap.Canvas.Pixels[w,h], cg);
end;
``````

Here we have two nested "for" loops. On my machine I have only two virtual cores and on a physical one - just four. That is why there is no point in turning both loops to "parallel". We only want to convert the "outer" one. Here is the code after "parallelization":
``````procedure ToGrayPixelsParallel(aBitmap: Graphics.TBitmap; cg: TColor2Grayscale = c2gLuminosity);
var h: integer;
begin
if aBitmap <> nil then
TParallel.For(0, aBitmap.Height-1,
procedure(h: integer)
var w: integer;
begin
for w := 0 to aBitmap.Width - 1 do
aBitmap.Canvas.Pixels[w,h] :=
ColorToGray(aBitmap.Canvas.Pixels[w,h], cg);
end
);
end;
``````

The key thing to look for is using local variables. Notice that I had to move "w: integer" local variable into the body of the anonymous method that is being executed. If I would not do it, the same variable would be shared by different threads of execution leading to errors.

And yes. The parallel versions of both "pixels" and "scan line" color to gray are working as expected on average two times faster on my two virtual cores inside of my virtual Windows machine running on Mac.

The source code of the test "PixelsVsScanLineParallelToGray" Delphi XE7 VCL project is available for download from Embarcadero Code Central.