Stefano Tommesani

  • Increase font size
  • Default font size
  • Decrease font size
Home Programming Further multi-thread processing with Delphi

Further multi-thread processing with Delphi

Hits

In a previous article named "Easy multi-thread programming Delphi", the AsyncCalls library was used to process multiple images at the same time. However, the processing of every single image was still strictly serial, even if image processing kernels are quite easy to accelerate spreading the load over multiple threads.

In this article we will see how the OmniThreadLibrary can be used to split a simple image processing kernel across multiple threads.

procedure TProcessedImage.EffectBlackWhite(Bitmap : TBitmap32);
var x, y : integer;
    Color : TColor32;
    Red, Green, Blue : Cardinal;
    Gray : Cardinal;
begin
  Bitmap.BeginUpdate;
  try
    for y := 0 to Pred(Bitmap.Height) do
        for x := 0 to Pred(Bitmap.Width) do
            begin
            Color := Bitmap.Pixel[x, y];
            Red := (Color and $00FF0000) shr 16;
            Green := (Color and $0000FF00) shr 8;
            Blue := Color and $000000FF;
            Gray := (Red * 299 + Green * 587 + Blue * 114) div 1000;
            //Io=(0.299Ri + 0.587Gi + 0.114Bi)
            Bitmap.Pixel[x, y] := Color32(Gray, Gray, Gray);
            end;
  except
  end;
  Bitmap.EndUpdate;
end;

This procedure converts a RGB image into a gray-scale image by replicating the luma value into all the RGB channels. The processing of each pixel is independent from other ones, so it can be computed in parallel. Still, allocating a thread for each pixel would generate a huge unnecessary overhead, so it is more efficient to process each line, containing Bitmap.Width pixels, in a separate thread. Moving to a parallel solution is very easy thanks to the OmniThreadLibrary:

  1. include OtlParallel into the uses list
  2. add a Parallel.ForEach().Execute() line that specifies the number of iterations of the loop, in this case the vertical rows of the image to be processed, so the loop counter goes from 0 to Pred(Bitmap.Height) as the loop handled by the y variable in the serial code
  3. move the bulk of the image processing code into a lambda inside the Execute invocation, and replace the variable y with the argument of the lambda called elem
  4. move the variables that work on a single pixel inside the lambda, outside of the parent procedure, or they will be shared across all the threads executing the lambda. If you encounter wrong results that vary at every run, check that all variables used to perform computations are declared in the lambda.
procedure TProcessedImage.EffectBlackWhite(Bitmap : TBitmap32);
begin
  Bitmap.BeginUpdate;
  try
    Parallel.ForEach(0, Pred(Bitmap.Height)).Execute(
    procedure (const elem: integer)
    var x : integer;
        Color : TColor32;
        Red, Green, Blue : Cardinal;
        Gray : Cardinal;
    begin
      for x := 0 to Pred(Bitmap.Width) do
          begin
          Color := Bitmap.Pixel[x, elem];
          Red := (Color and $00FF0000) shr 16;
          Green := (Color and $0000FF00) shr 8;
          Blue := Color and $000000FF;
          Gray := (Red * 299 + Green * 587 + Blue * 114) div 1000;
          //Io=(0.299Ri + 0.587Gi + 0.114Bi)
          Bitmap.Pixel[x, elem] := Color32(Gray, Gray, Gray);
          end;
    end);
  except
  end;
  Bitmap.EndUpdate;
end;

That's all! I told you this was going to be easy... Thanks to the OmniThreadLibrary, splitting a computational load over multiple CPU cores is now a task that can be accomplished in a matter of minutes.

Even better, we can use OmniThreadLibrary to build DUnit test cases that check for code correctness under multi-threaded usage. The following code fragment tests that adding status updates and queries can be run at the same time on the database backend:

procedure TestTFWDDatabase.TestMultiThread3;
const UpdatedUserName : string = 'UpdatedUser';
      UpdaterUserName : string = 'UpdaterUser';
var UpdatedUser : TFWDUser;
    UpdaterUser : TFWDUser;
    CleanupStatusChangeList : TList<TFWDStatusChange>;
    StatusChangePtr : TFWDStatusChange;
begin
  /// create user to be updated
  UpdatedUser := TFWDUser.Create;
  UpdatedUser.ProtocolId := UpdatedUserName;
  ObjectDatabase.AddUser(UpdatedUser);

  /// create user that will signal updates
  UpdaterUser := TFWDUser.Create;
  UpdaterUser.ProtocolId := UpdaterUserName;
  ObjectDatabase.AddUser(UpdaterUser);

  /// add updates and run queries on update table at the same time
  Parallel.ForEach(1, 100).Execute(
    procedure (const elem: integer)
    var TestStatusUpdate : TFWDStatusUpdate;
        StatusChangeList : TList<TFWDStatusChange>;
    begin
    if (elem and 1) = 0
       then begin
            /// status update
            TestStatusUpdate := TFWDStatusUpdate.Create(UpdaterUserName, PROTOCOL_FACEBOOK);
            TestStatusUpdate.AddFriendStatusUpdate(UpdatedUserName, false, elem);
            TestStatusUpdate.Process;
            TestStatusUpdate.Free;
            end
       else begin
            /// run query
            StatusChangeList := ObjectDatabase.GetStatusChangesFromUserId(UpdatedUser.Id);
            StatusChangeList.Free;
            end;
    end
  );

  CleanupStatusChangeList := ObjectDatabase.GetStatusChangesFromUserId(UpdatedUser.Id);
  Check(CleanupStatusChangeList.Count = 50, 'Wrong count of status changes');
  Check(UpdaterUser.UpdateCount = 50, 'Wrong count of status updates');
  /// additional tests hidden...

  /// cleanup
  for StatusChangePtr in CleanupStatusChangeList do
      ObjectDatabase.EraseStatusChange(StatusChangePtr);
  CleanupStatusChangeList.Free;

  ObjectDatabase.EraseUser(UpdatedUser);
  ObjectDatabase.EraseUser(UpdaterUser);
end;

The Parallel.ForEach runs 100 iterations of the lambda function, that uses the elem parameter to choose which action shall be performed in that invocation, choosing between adding a status update on even iterations and running a query on status tables on odd iterations. This code fragment is a part of the DUnit test suite of the Friend Watchdog server, and together with a large set of the other automated unit tests ensures that the server can support multiple requests from Friend Watchdog clients without getting stuck or corrupting data.

Quote this article on your site

To create link towards this article on your website,
copy and paste the text below in your page.




Preview :


Powered by QuoteThis © 2008
Last Updated on Monday, 22 April 2013 15:45  

Latest Articles

A software to stand out 27 January 2018, 14.35 Web
A software to stand out
Standing out of the pack starts by being visible, and being noticed by the right group of professionals. No matter how good your profile is, it is lost in a sea of similar profiles, so you need to show up and start attracting
Web page scraping, the easy way 07 January 2018, 00.46 Web
Web page scraping, the easy way
There are many ways to extract data elements from web pages, almost all of them prettier and cooler than the method proposed here, but as we are in an hurry, let's get that data quickly, ok? Suppose we have to extract the
Scraping dynamic page content 06 January 2018, 23.57 Web
Scraping dynamic page content
One of the most common roadblocks when scraping the content of web sites is getting the full contents of the page, including JS-generated data elements (probably, the ones you are looking for). So, when using CEFSharp to scrape
Unit-testing file I/O 26 November 2017, 12.09 Testing
Unit-testing file I/O
Two good news: file I/O is unit-testable, and it is surprisingly easy to do. Let's see how it works! A software no-one asked for First, we need a piece of software that deals with files and that has to be unit-tested. The
Fixing Git pull errors in SourceTree 10 April 2017, 01.44 Software
Fixing Git pull errors in SourceTree
If you encounter the following error when pulling a repository in SourceTree: VirtualAlloc pointer is null, Win32 error 487 it is due to to the Cygwin system failing to allocate a 5 MB large chunk of memory for its heap at