Results 1 to 7 of 7

Thread: Tuning QtConcurrent::map()

  1. #1
    Join Date
    Jan 2009
    Location
    Germany
    Posts
    387
    Thanks
    101
    Thanked 15 Times in 15 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Tuning QtConcurrent::map()

    Hi there,

    I am experimenting with the QtConcurrent framework in order to launch multiple, long running threads that compute some scientific stuff.
    I use a blocking QtConcurrent::blockingMap call with a member function like so:

    Qt Code:
    1. void Experimenter::runExperiments()
    2. {
    3. QtConcurrent::blockingMap(cases, [this] (ExperimentConfig& ex) { processCase(ex); });
    4. }
    5.  
    6. void Experimenter::processCase(ExperimentConfig& ex)
    7. {
    8. // compute many things
    9. }
    To copy to clipboard, switch view to plain text mode 

    This works fine as it launches some 20 threads on my machine, but only one of these threads is actively working. I'm observing the threads in htop and I can see that only one core and one process is peaked to near 100%. The machine has plenty of memory available and barely 10% of it is used. Any ideas as to how I can make this more efficient?

  2. #2
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    5,230
    Thanks
    302
    Thanked 864 Times in 851 Posts
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Tuning QtConcurrent::map()

    Any ideas as to how I can make this more efficient?
    If I am decoding the documentation correctly, it seems that QtConcurrent::blockingMap() does create multiple threads, but they run sequentially and block until the entire sequence has been processed. You should probably try using QtConcurrent::map() and QFutureWatcher instead to run the calculations in parallel and be notified when all have finished.

    Is your "sequence" (cases) access-protected by a mutex or semaphore? If my understanding of the documentation is incorrect, then locking the sequence when a thread is running could be the cause, since it would be blocking other threads from accessing their elements of the sequence.
    Last edited by d_stranz; 9th July 2021 at 17:46.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  3. The following user says thank you to d_stranz for this useful post:

    Cruz (12th July 2021)

  4. #3
    Join Date
    Jan 2009
    Location
    Germany
    Posts
    387
    Thanks
    101
    Thanked 15 Times in 15 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Tuning QtConcurrent::map()

    I have tried three different approaches to this multithreading problem and I am observing the same result no matter which way I go.
    The first version is the blocking QtConcurrent::blockingMap() call:

    Qt Code:
    1. class Experimenter : public QThread
    2. {
    3. QVector<ExperimentConfig> cases;
    4. }
    5.  
    6. void Experimenter::runExperiments()
    7. {
    8. QtConcurrent::blockingMap(cases, [this] (ExperimentConfig& ex) { processCase(ex); });
    9. }
    10.  
    11. void Experimenter::processCase(ExperimentConfig& ex)
    12. {
    13. // compute many things
    14. }
    To copy to clipboard, switch view to plain text mode 

    In the second version, I use QtConcurrent::run() to launch threads and watch QFuture objects to see when a thread has finished:
    Qt Code:
    1. void Experimenter::runExperiments()
    2. {
    3. const static uint threads = 5;
    4. Vector<QFuture<void> > results(threads);
    5. bool finished = false;
    6. uint k = 0;
    7. while (!finished)
    8. {
    9. for (uint i = 0; i < threads; i++)
    10. {
    11. if (results[i].isFinished() && k < cases.size())
    12. {
    13. qDebug() << "Thread" << i << "is now working on case" << k;
    14. results[i] = QtConcurrent::run(this, &Experimenter::processCase, cases[k++]);
    15. }
    16. }
    17.  
    18. if (k >= cases.size())
    19. finished = true;
    20. sleep(10);
    21. }
    22. }
    To copy to clipboard, switch view to plain text mode 

    In the third version, I start() my own QThread objects and watch their isRunning() status to see when they have finished:

    Qt Code:
    1. class WorkerThread : public QThread
    2. {
    3. public:
    4.  
    5. ExperimentConfig ex;
    6. void run()
    7. {
    8. processCase(ex);
    9. }
    10.  
    11. void processCase(ExperimentConfig& ex);
    12. };
    13.  
    14. void Experimenter::runExperiments()
    15. {
    16. const static uint threads = 5;
    17. WorkerThread thread[threads];
    18. bool finished = false;
    19. uint k = 0;
    20. while (!finished)
    21. {
    22. for (uint i = 0; i < threads; i++)
    23. {
    24. if (!thread[i].isRunning() && k < cases.size())
    25. {
    26. qDebug() << "Thread" << i << "is now working on case" << k;
    27. cases[thread[i].ex.id] = thread[i].ex;
    28. thread[i].ex = cases[k++];
    29. thread[i].start();
    30. }
    31. }
    32.  
    33. if (k >= cases.size())
    34. finished = true;
    35. sleep(10);
    36. }
    37. }
    To copy to clipboard, switch view to plain text mode 

    The observed behavior in all cases is that the threads are interleaved correctly, they do run at the same time, but apparently they are all run on the same core. In htop, I can see only one core peaking at 100% and the threads I created are fluctuating between 0% an 100%, mostly one at a time, sometimes two threads adding up to 100%. It seems to me that all started threads are processed on the same core and are thus interrupted a lot by round robin. When I start a second process that again launches its own threads, I can see two cores being loaded to 100%. Why aren't the threads running on different cores?

    The question whether cases are mutexed is good thinking, but unfortunately no, this is not the case. Or at least not that I am aware of.
    cases is a QVector of objects as shown in the first code snippet above. Parallel access through the [] operator should be no problem, right?
    Otherwise, the threads give qDebug output and perform unmutexed file write operations which hasn't been an issue so far.

  5. #4
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    5,230
    Thanks
    302
    Thanked 864 Times in 851 Posts
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Tuning QtConcurrent::map()

    Why aren't the threads running on different cores?
    No idea. The docs say that Qt Concurrent is designed to take advantage of multiple cores and to scale as the number of cores increases.

    I wonder if there is an OS setting you need to change to allow a process to spawn threads across multiple cores?
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  6. The following user says thank you to d_stranz for this useful post:

    Cruz (12th July 2021)

  7. #5
    Join Date
    Jan 2009
    Location
    Germany
    Posts
    387
    Thanks
    101
    Thanked 15 Times in 15 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Tuning QtConcurrent::map()

    I replaced my complex processCase() function with a few simple lines that stress the CPU and it turns out the threading through QtConcurrent works as expected. The started threads execute at 100% on different cores. So it turns out the error was indeed a mutex somewhere deep down that all threads tried to lock and so they could run only one at a time. This is embarassing, but thanks for the help!

  8. #6
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    5,230
    Thanks
    302
    Thanked 864 Times in 851 Posts
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Tuning QtConcurrent::map()

    That's good to know. So I guess the difference between the blocking and non-blocking QtConcurrent map methods is that the method containing the call to QtConcurrent::blockingMap() will not return until all concurrent processing is complete, whereas QtConcurrent::map() launches the threads and returns immediately.

    From a design perspective, is there a reason why you chose to use the blocking call?
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  9. #7
    Join Date
    Jan 2009
    Location
    Germany
    Posts
    387
    Thanks
    101
    Thanked 15 Times in 15 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Tuning QtConcurrent::map()

    Quote Originally Posted by d_stranz View Post
    From a design perspective, is there a reason why you chose to use the blocking call?
    Because with blockingMap() it's simpler to do things after all threads have finished. For example, I save the computed data after all threads have run. For everything else, you have to watch QFuture objects in a loop or set up additional signal and slot connections to know when all threads are finished.

  10. The following user says thank you to Cruz for this useful post:

    d_stranz (12th July 2021)

Similar Threads

  1. Anomaly browser tuning
    By Bit in forum Qt for Embedded and Mobile
    Replies: 0
    Last Post: 25th June 2012, 16:57
  2. Failed to build Qt 4.7.1 in a shadow build with feature tuning
    By myfifth in forum Installation and Deployment
    Replies: 0
    Last Post: 17th February 2011, 04:25
  3. QtConcurrent Threads
    By mlheese in forum Newbie
    Replies: 1
    Last Post: 30th July 2010, 06:35
  4. qtconcurrent
    By knishaq in forum Qt Programming
    Replies: 4
    Last Post: 15th December 2009, 09:41
  5. QtConcurrent
    By Brandybuck in forum An Introduction to QThreads
    Replies: 2
    Last Post: 9th May 2008, 15:10

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.