Author Topic: PCL: image status for threaded processes  (Read 6958 times)

Offline Nocturnal

  • PixInsight Jedi Council Member
  • *******
  • Posts: 2727
    • http://www.carpephoton.com
PCL: image status for threaded processes
« on: 2009 April 26 20:52:17 »
Hi,

before attempting to add more complex debayering methods to my module I first want to make it multi threaded. I'm looking at the ColorSaturation code as an example. Porting it for my evil purposes was pretty easy but my code makes PI bomb pretty hard. The debugger isn't much help in identifying what's going on. I want to simplify my code to the bare minimum so I'm curious what this bit is all about:

         if ( ++n1 == REFRESH_COUNT )
         {
            n1 = 0;
            n += REFRESH_COUNT;
            if ( mutex->TryLock() )
            {
               count += n;
               bool myAbort = abort;
               mutex->Unlock();
               if ( myAbort )
                  break;
               n = 0;
            }
         }


Why are threads updating the global variable count? Why is the supervisor main thread monitoring it?

            img.Status() += count - lastCount;

Can I safely remove all references to img.Status and related code?

My threading model is very simple. I create a target Image and tell each thread to do part of it. Since each thread writes to its own section of the image there is no collision. In other sample code I've seen that threads create buffers that are then copied into the image upon completion. I'd like to skip that step if possible unless the image memory can't be accessed by more than 1 thread at the same time?
Best,

    Sander
---
Edge HD 1100
QHY-8 for imaging, IMG0H mono for guiding, video cameras for occulations
ASI224, QHY5L-IIc
HyperStar3
WO-M110ED+FR-III/TRF-2008
Takahashi EM-400
PIxInsight, DeepSkyStacker, PHD, Nebulosity

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
PCL: image status for threaded processes
« Reply #1 on: 2009 April 27 02:19:38 »
Hi Sander,

Welcome to the exciting world of multithreaded image processing :)

The following construct:

Code: [Select]
if ( ++n1 == REFRESH_COUNT )
{
   n1 = 0;
   n += REFRESH_COUNT;
   if ( mutex->TryLock() )
   {
      count += n;
      bool myAbort = abort;
      mutex->Unlock();
      if ( myAbort )
         break;
      n = 0;
   }
}


allows a thread to communicate with the GUI thread (or the main thread; we call it the GUI thread because only the main thread can perform direct GUI operations) to provide progress information feedback on the console. In addition, when the GUI thread receives control it can respond to a request to abort the ongoing process (for example, when the user clicks the Pause/Abort button).

A few key things must be pointed out in the code above. One is the use of Mutex::TryLock() instead of Mutex::Lock(). Thread synchronization is a main cornerstone of parallel processing. Bad thread synchronization can easily degrade performance of parallel code to the point that nonparallel code can perform better.

TryLock() attempts to lock a Mutex object. If it cannot lock it (because other thread has already locked it), it gives up immediately and returns false. Otherwise, if it successfully locks the mutex, TryLock() returns true. In contrast, Lock() waits until it can lock the mutex. In this case, TryLock() allows us to provide progress feedback at virtually zero cost in terms of thread performance, since no special efforts are done to lock mutexes. Of course, this is possible in this case because progress information isn't an essential part of the process, so a thread can continue without problems if TryLock() returns false.

Another important fact that must be pointed out, is that after having locked a mutex, it should be unlocked as soon as possible. Note that in the code above we perform exactly the necessary actions while the mutex is locked: we update the global count variable, get a local copy of the global abort flag, and then call Mutex::Unlock() immediately. Doing things fast while mutexes are locked greatly optimizes thread synchronization performance.

Finally, in the code above the REFRESH_COUNT macro defines the number of pixels that the thread can process before trying to update progress information. REFRESH_COUNT must be something like 65536, which means that the thread will process 64K pixels (the exact number isn't important) without caring about providing feedback. This is important because if we call Mutex::TryLock() too often, performance degradation is served.

Quote
Can I safely remove all references to img.Status and related code?


Of course you can. You can launch a set of threads that run without any user feedback or GUI interaction. Obviously, the user will be unable to stop your process and the GUI will be freezed. The decision depends on the processing time. For example, typically we use constructs like this:

Code: [Select]
IndirectArray<WorkerThread> threads;

int numberOfThreads = Thread::NumberOfThreads( numberOfItems, 1 );
int itemsPerThread = numberOfItems/numberOfThreads;

for ( int i = 0; i < numberOfThreads; ++i )
   threads.Add( new WorkerThread( this, i*itemsPerThread, (i < numberOfThreads-1) ? (i+1)*itemsPerThread : numberOfItems ) );

for ( int i = 0; i < numberOfThreads; ++i )
   threads[i]->Start( ThreadPriority::TimeCritical );
for ( int i = 0; i < numberOfThreads; ++i )
   threads[i]->Wait();

threads.Destroy();


to process a set of items by nonoverlapping chunks with parallel code.

Quote
I create a target Image and tell each thread to do part of it. Since each thread writes to its own section of the image there is no collision.


If there are no concurrent writes life is much simpler with threads since you need no synchronization: just fire up your threads and wait until all of them have finished, as in the code snippet above. This is of course the best option in terms of performance.
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline Nocturnal

  • PixInsight Jedi Council Member
  • *******
  • Posts: 2727
    • http://www.carpephoton.com
PCL: image status for threaded processes
« Reply #2 on: 2009 April 27 07:02:41 »
Hi Juan,

I'm generally familiar and experienced with threaded programming so don't worry about explaining mutexes and that sort of thing. I just need to know what the threading requirements are for PCL. Every time I call a PCL method I have to worry if it's thread safe for example or if I need to provide exclusive access myself. It could also be that PCL expects certain things to happen in threads, such as this GUI update business, that if left out cause problems. Just to save you a bunch of typing :)

I will first make a simple version of my threads work and then add GUI updates. Divide and conquer or stepwise improvement.
Best,

    Sander
---
Edge HD 1100
QHY-8 for imaging, IMG0H mono for guiding, video cameras for occulations
ASI224, QHY5L-IIc
HyperStar3
WO-M110ED+FR-III/TRF-2008
Takahashi EM-400
PIxInsight, DeepSkyStacker, PHD, Nebulosity

Offline Nocturnal

  • PixInsight Jedi Council Member
  • *******
  • Posts: 2727
    • http://www.carpephoton.com
PCL: image status for threaded processes
« Reply #3 on: 2009 April 27 15:01:57 »
I guess this says it all:

Code: [Select]

PixInsight Core 01.04.05.0467 (x86)
Copyright © 2003-2009 Pleiades Astrophoto
----------------------------------------------------------------------
Welcome to PixInsight. Started 2009 Apr 27 21:57:58 J2454949.41526 UTC

* Parallel processing enabled: Using 2 logical processors.
* PSM AutoSave enabled. Auto-save period: 30 seconds.

Reading 1 file(s):
c:/temp/Series1_006_fixfits.fts
Reading FITS: 16-bit integers, 1 channel(s), 3040x2016 pixels: 100%

Debayer: Processing view: Series1_006_fixfits
* Using 2 logical processors.
SuperPixel Debayering: 100%
1.235 s


With the thread count artificially reduced to 1 the time goes up to 2.047s. This is with debug code though.

The problem was that I was using rows from the original image instead of the destination image to split the work. It seems to work fine now. Onto Binlinear.
Best,

    Sander
---
Edge HD 1100
QHY-8 for imaging, IMG0H mono for guiding, video cameras for occulations
ASI224, QHY5L-IIc
HyperStar3
WO-M110ED+FR-III/TRF-2008
Takahashi EM-400
PIxInsight, DeepSkyStacker, PHD, Nebulosity

Offline Nocturnal

  • PixInsight Jedi Council Member
  • *******
  • Posts: 2727
    • http://www.carpephoton.com
Re: PCL: image status for threaded processes
« Reply #4 on: 2009 April 28 20:03:41 »
Status now works about 80%. I am not fully sure it's really counting all the way to 100% before calling it done. That's a minor issue though. The process completes and ends up with 100% due to that last magic line of code that enforces that. I suspect Juan had a few cases where the percentage got stuck below 100 even though all threads reported done :)
Best,

    Sander
---
Edge HD 1100
QHY-8 for imaging, IMG0H mono for guiding, video cameras for occulations
ASI224, QHY5L-IIc
HyperStar3
WO-M110ED+FR-III/TRF-2008
Takahashi EM-400
PIxInsight, DeepSkyStacker, PHD, Nebulosity

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: PCL: image status for threaded processes
« Reply #5 on: 2009 April 29 00:31:46 »
Quote
The process completes and ends up with 100% due to that last magic line of code that enforces that. I suspect Juan had a few cases where the percentage got stuck below 100 even though all threads reported done

Since we use Mutex::TryLock(), there is no guarantee that the final progress count reaches the total monitoring count, because many attempts to update the global count variable are likely to fail. With Mutex::Lock() this can be avoided, but at the cost of some performance degradation. Of course, we don't want to degrade performance for the sole purpose of writing to the console. So the last line does the magic :)
Juan Conejero
PixInsight Development Team
http://pixinsight.com/