David
David

Reputation: 65

Optimize performance of a Parallel.For

I have replaced a for loop in my code with a Parallel.For. The performance improvement is awesome (1/3 running time). I've tried to account for shared resources using an array to gather result codes. I then process the array out side the Parallel.For. Is this the most efficient way or will blocking still occur even if no iteration can ever share the same loop-index? Would a CompareExchange perform much better?

int[] pageResults = new int[arrCounter];
Parallel.For(0, arrCounter, i =>
{
   AlertToQueueInput input = new AlertToQueueInput();
   input.Message = Messages[i];

   pageResults[i] = scCommunication.AlertToQueue(input).ReturnCode;
});

foreach (int r in pageResults)
{
    if (r != 0 && outputPC.ReturnCode == 0) outputPC.ReturnCode = r;
}

Upvotes: 3

Views: 570

Answers (3)

Henk Holterman
Henk Holterman

Reputation: 273844

It depends on whether you have any (valuable) side-effects in the main loop.

When the outputPC.ReturnCode is the only result, you can use PLINQ:

outputPC.ReturnCode = Messages
    .AsParallel()
    .Select(msg =>    
    {
       AlertToQueueInput input = new AlertToQueueInput();
       input.Message = msg;        
       return scCommunication.AlertToQueue(input).ReturnCode;
    })
    .FirstOrDefault(r => r != 0);

This assumes scCommunication.AlertToQueue() is thread-safe and you don't want to call it for the remaining items after the first error.

Note that FirstOrDefault() in PLinq is only efficient in Framework 4.5 and later.

Upvotes: 6

Max Novich
Max Novich

Reputation: 1169

I like David Arno's solution, but as I see you can improve the speed with putting the check inside the parallel loop and breaking directly from it. Anyway you put the main code to fail if any of iterations failed , so there is no need for further iterations.

Something like this:

Parallel.For(0, arrCounter, (i, loopState) =>
{
   AlertToQueueInput input = new AlertToQueueInput();
   input.Message = Messages[i];
   var code = scCommunication.AlertToQueue(input).ReturnCode;
    if (code != 0)
    {
        outputPC.ReturnCode = code ;
         loopState.Break();
    }

});

Upd 1:

If you need to save the result of all iterations you can do something like this:

int[] pageResults = new int[arrCounter];
Parallel.For(0, arrCounter, (i, loopState) =>
    {
       AlertToQueueInput input = new AlertToQueueInput();
       input.Message = Messages[i];
       var code = scCommunication.AlertToQueue(input).ReturnCode;
       pageResults[i] = code ;
        if (code != 0 && outputPC.ReturnCode == 0)
            outputPC.ReturnCode = code ;    
    });

It will save you from the foreach loop which is an improvement although a small one.

UPD 2:

just found this post and I think custom parallel is a good solution too. But it's your call to decide if it fits to your task.

Upvotes: 3

David Arno
David Arno

Reputation: 43264

You could replace:

foreach (int r in pageResults)
{
    if (r != 0 && outputPC.ReturnCode == 0) outputPC.ReturnCode = r;
}

with:

foreach (int r in pageResults)
{
    if (r != 0)
    {
        outputPC.ReturnCode = r;
        break;
    }
}

This will then stop the loop on the first fail.

Upvotes: 3

Related Questions