Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Is QtConcurrent suitable for this concurrent/parallel algorithm?
Qt 6.11 is out! See what's new in the release blog

Is QtConcurrent suitable for this concurrent/parallel algorithm?

Scheduled Pinned Locked Moved Unsolved General and Desktop
9 Posts 4 Posters 391 Views 3 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • JonBJ Offline
    JonBJ Offline
    JonB
    wrote last edited by JonB
    #1

    I feel like testing my new laptop's speed/core threading... :)

    My test will be calculating whether an input number is prime or not. The size of the number will be "large", so Eratosthenes Sieve would cost too much space. Instead I will run trial division. So the initial, single-threaded algorithm will be:

    long dividend = ...;  // the number to test for primality
    long limit = sqrt(test);
    for (long divisor = 2; divisor <= limit; divisor++)
        if (dividend % divisor == 0)
            return false;
    return  true;
    

    Now the question is to how to split the task "optimally" for concurrent execution across available threads/cores.

    If I naively just set this off with some QtConcurrent method and a function/lambda which just returns the dividend % divisor == 0 result for each number in range 2..limit I presume there will be some overhead for initiating/terminating each thread which has only done a simple dividing and that will be far from optimal. Right?

    If I were to do this myself with threads/cores I would go for something like:

    int threads = available_threads();  // maybe 8?
    for (int thread = 0; thread < threads; thread++)
    {
        threadObj = createThread();
        threadObj->run(threadFunc, 2 + thread, limit, threads);
    }
    if (any_thread_returns_false())
        return false;
    return true;
    
    bool threadFunc(long start, long limit, long step)
    {
        for (long divisor = start; divisor <= limit; divisor += step)
            if (dividend % divisor == 0) 
                return false;
        return  true;
    }
    

    This partitions the range into available_threads() separate ranges via steps, so each created thread tests each of these sub-ranges, no further thread creation/destruction over the initial creation of threads.

    Obviously I then need (a) some mechanism of knowing when all threads have run their own loops to completion and never found a divisor for the dividend (so tested number is indeed prime) and (b) a way of a thread returning or signalling immediately when it has found a divisor (tested number is composite) so that main code can then immediately terminate the other threads and return false.

    Among all the QtConcurrent methods for filtering/mapping/reducing I have not figured whether/how this algorithmic behaviour could be executed as stated? (Btw, any solution which creates a list of all the numbers from 2 to limit and then filters/reduces them takes too much space by definition; and anything which creates more total threads ever greater than available_threads() is presumed to be "slow" because of thread creation overhead.)

    For the avoidance of doubt: I am interested in Qt methods to test performance. If there is, say, a std library function which tells you whether a number is prime even by using the host's threads for you, that is not what I am looking for :)

    JKSHJ 1 Reply Last reply
    0
    • JonBJ Offline
      JonBJ Offline
      JonB
      wrote last edited by JonB
      #2

      Thinking aloud about my own post. Funny how typing it all in makes you think down a logical route... :)

      I was thinking/hoping in terms of calling a QtConcurrent method for each division to test and having it just do the dividend % divisor == 0 for one pair of numbers. But as I said that will presumably incur too much overhead. If I want to use QtConcurrent: I know (I think) that I only want available_threads() number of threads created, so I could use it to create those with threadFunc(long start, long limit, long step) as the function to run. I am then using QtConcurrent to handle the early or final termination code of all threads (the "outer" loop) rather than at the "do one division" (the "inner" loop). (Looks like QtConcurrent::run() in Run With Promise mode?)

      Is this the (only/right) way to use QtConcurrent for my proposed algorithm?

      1 Reply Last reply
      0
      • I Offline
        I Offline
        IgKh
        wrote last edited by IgKh
        #3

        I'd probably use a filter-reduce operation on a custom iterator class (to not actually allocate a list of all numbers 2..sqrt(n)) - https://doc.qt.io/qt-6/qtconcurrentfilter.html#concurrent-filter-reduce

        Also - I know you didn't ask for alternative algorithms, but for the benefit of anyone reading the thread later that might be interested, the most efficient way to test primality of truly large numbers is the Rabin-Miller test

        JonBJ 1 Reply Last reply
        1
        • I IgKh

          I'd probably use a filter-reduce operation on a custom iterator class (to not actually allocate a list of all numbers 2..sqrt(n)) - https://doc.qt.io/qt-6/qtconcurrentfilter.html#concurrent-filter-reduce

          Also - I know you didn't ask for alternative algorithms, but for the benefit of anyone reading the thread later that might be interested, the most efficient way to test primality of truly large numbers is the Rabin-Miller test

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote last edited by
          #4

          @IgKh said in Is QtConcurrent suitable for this concurrent/parallel algorithm?:

          I'd probably use a filter-reduce operation on a custom iterator class (to not actually allocate a list of all numbers 2..sqrt(n)) - https://doc.qt.io/qt-6/qtconcurrentfilter.html#concurrent-filter-reduce

          Yes, I did wonder whether a "lazy-generator" of the numbers might be usable for the QtConcurrent methods. I might try this as a timing test against my proposed approach outlined earlier.

          the most efficient way to test primality of truly large numbers is the Rabin-Miller test

          I had a read through this. I am aware there are other algorithms which perform better than trial division. But, if I understand correctly, it (a) only tells me whether a number is probably prime, (b) relies (at least in some variants) on a still unproven Riemann hypothesis and (c) does not give me factors if it decides a number is composite. I am uncomfortable about all of these! Of course, I do realise my intended range of 64-bit numbers is trivially small and doubtless the assumptions do hold in this domain, but I still don't like the idea of a mathematical solution which says a 3 sided shape is "probably" a triangle... ;-)

          I 1 Reply Last reply
          0
          • JonBJ JonB

            @IgKh said in Is QtConcurrent suitable for this concurrent/parallel algorithm?:

            I'd probably use a filter-reduce operation on a custom iterator class (to not actually allocate a list of all numbers 2..sqrt(n)) - https://doc.qt.io/qt-6/qtconcurrentfilter.html#concurrent-filter-reduce

            Yes, I did wonder whether a "lazy-generator" of the numbers might be usable for the QtConcurrent methods. I might try this as a timing test against my proposed approach outlined earlier.

            the most efficient way to test primality of truly large numbers is the Rabin-Miller test

            I had a read through this. I am aware there are other algorithms which perform better than trial division. But, if I understand correctly, it (a) only tells me whether a number is probably prime, (b) relies (at least in some variants) on a still unproven Riemann hypothesis and (c) does not give me factors if it decides a number is composite. I am uncomfortable about all of these! Of course, I do realise my intended range of 64-bit numbers is trivially small and doubtless the assumptions do hold in this domain, but I still don't like the idea of a mathematical solution which says a 3 sided shape is "probably" a triangle... ;-)

            I Offline
            I Offline
            IgKh
            wrote last edited by IgKh
            #5

            @JonB said in Is QtConcurrent suitable for this concurrent/parallel algorithm?:

            But, if I understand correctly, it (a) only tells me whether a number is probably prime, (b) relies (at least in some variants) on a still unproven Riemann hypothesis and (c) does not give me factors if it decides a number is composite.

            We are going a little off-topic, but this is close to the area of my professional capacity so why not! Rabin-Miller is indeed probabilistic, but the error probability can be arbitrarily bounded by running more iterations of the test, driving the chance of mistake to being negligible. This is similar in concept to other probabilistic data structure and algorithms, like bloom filters. The Riemann hypothesis thing is not actually relied on if you accept non-determinism, so the matter of assumptions holding is not pertinent to the size of the numbers.

            It's true that RM is just a test, it doesn't help to factorize integers - at least not directly. It is however very important in cryptography, since many cryptosystems rely on generating very large random prime numbers. And by very large I mean things like 2,048 or 4,096 bits. A loop of drawing a random number from a range and then testing it for primality is the best we have in practice, so a fast (even if only, say, 99.9999% accurate) test is extremely useful.

            JonBJ 1 Reply Last reply
            0
            • I IgKh

              @JonB said in Is QtConcurrent suitable for this concurrent/parallel algorithm?:

              But, if I understand correctly, it (a) only tells me whether a number is probably prime, (b) relies (at least in some variants) on a still unproven Riemann hypothesis and (c) does not give me factors if it decides a number is composite.

              We are going a little off-topic, but this is close to the area of my professional capacity so why not! Rabin-Miller is indeed probabilistic, but the error probability can be arbitrarily bounded by running more iterations of the test, driving the chance of mistake to being negligible. This is similar in concept to other probabilistic data structure and algorithms, like bloom filters. The Riemann hypothesis thing is not actually relied on if you accept non-determinism, so the matter of assumptions holding is not pertinent to the size of the numbers.

              It's true that RM is just a test, it doesn't help to factorize integers - at least not directly. It is however very important in cryptography, since many cryptosystems rely on generating very large random prime numbers. And by very large I mean things like 2,048 or 4,096 bits. A loop of drawing a random number from a range and then testing it for primality is the best we have in practice, so a fast (even if only, say, 99.9999% accurate) test is extremely useful.

              JonBJ Offline
              JonBJ Offline
              JonB
              wrote last edited by
              #6

              @IgKh
              I read a whole book just on the Riemann Hypothesis a few years ago. I can honestly say it was the most boring book I have ever read ;-)

              1 Reply Last reply
              0
              • JonBJ JonB

                I feel like testing my new laptop's speed/core threading... :)

                My test will be calculating whether an input number is prime or not. The size of the number will be "large", so Eratosthenes Sieve would cost too much space. Instead I will run trial division. So the initial, single-threaded algorithm will be:

                long dividend = ...;  // the number to test for primality
                long limit = sqrt(test);
                for (long divisor = 2; divisor <= limit; divisor++)
                    if (dividend % divisor == 0)
                        return false;
                return  true;
                

                Now the question is to how to split the task "optimally" for concurrent execution across available threads/cores.

                If I naively just set this off with some QtConcurrent method and a function/lambda which just returns the dividend % divisor == 0 result for each number in range 2..limit I presume there will be some overhead for initiating/terminating each thread which has only done a simple dividing and that will be far from optimal. Right?

                If I were to do this myself with threads/cores I would go for something like:

                int threads = available_threads();  // maybe 8?
                for (int thread = 0; thread < threads; thread++)
                {
                    threadObj = createThread();
                    threadObj->run(threadFunc, 2 + thread, limit, threads);
                }
                if (any_thread_returns_false())
                    return false;
                return true;
                
                bool threadFunc(long start, long limit, long step)
                {
                    for (long divisor = start; divisor <= limit; divisor += step)
                        if (dividend % divisor == 0) 
                            return false;
                    return  true;
                }
                

                This partitions the range into available_threads() separate ranges via steps, so each created thread tests each of these sub-ranges, no further thread creation/destruction over the initial creation of threads.

                Obviously I then need (a) some mechanism of knowing when all threads have run their own loops to completion and never found a divisor for the dividend (so tested number is indeed prime) and (b) a way of a thread returning or signalling immediately when it has found a divisor (tested number is composite) so that main code can then immediately terminate the other threads and return false.

                Among all the QtConcurrent methods for filtering/mapping/reducing I have not figured whether/how this algorithmic behaviour could be executed as stated? (Btw, any solution which creates a list of all the numbers from 2 to limit and then filters/reduces them takes too much space by definition; and anything which creates more total threads ever greater than available_threads() is presumed to be "slow" because of thread creation overhead.)

                For the avoidance of doubt: I am interested in Qt methods to test performance. If there is, say, a std library function which tells you whether a number is prime even by using the host's threads for you, that is not what I am looking for :)

                JKSHJ Offline
                JKSHJ Offline
                JKSH
                Moderators
                wrote last edited by JKSH
                #7

                Back to using Qt Concurrent for your algorithm:

                @JonB said in Is QtConcurrent suitable for this concurrent/parallel algorithm?:

                Among all the QtConcurrent methods for filtering/mapping/reducing I have not figured whether/how this algorithmic behaviour could be executed as stated? (Btw, any solution which creates a list of all the numbers from 2 to limit and then filters/reduces them takes too much space by definition

                Instead of a list of all numbers to test, generate a list of ranges ("arithmetic progressions" in maths parlance) to test:

                struct Range {
                    long start;
                    long limit;
                    long step;
                };
                

                Then, apply QtConcurrent::map() to your QList<Range>. This is the idiomatic way^ to express your algorithm above (both are equivalent).

                BTW, it's pointless/wasteful to test even divisors (except 2). Just check if your number is divisible by 2 before starting any threads, and then let your threads only test odd divisors. This halves the maximum number of divisors that they need to test.

                Obviously I then need (a) some mechanism of knowing when all threads have run their own loops to completion and never found a divisor for the dividend (so tested number is indeed prime) and (b) a way of a thread returning or signalling immediately when it has found a divisor (tested number is composite)

                QFutureWatcher is your friend.

                so that main code can then immediately terminate the other threads and return false.

                Once an instance of your threadFunc() starts running, it normally can't be terminated before completion (in contrast, QThread offers a terminate() function). So, you'll need to add some kind of escape hatch, like a global Boolean flag that your for-loops check before performing a division. I'm not sure how much overhead this adds -- benchmark and see.

                @JonB said in Is QtConcurrent suitable for this concurrent/parallel algorithm?:

                (Looks like QtConcurrent::run() in Run With Promise mode?)

                Is this the (only/right) way to use QtConcurrent for my proposed algorithm?

                As you've seen above, Run, Map, and Filter are all viable. I'd imagine that the global Boolean flag has equal or lower overhead than cancelling a Run via a QPromise. However, the latter is more "self-contained" so you could run your prime-check algorithm on multiple dividends simultaneously.

                ^ P.S. To be extra idiomatic, you could use map-reduce instead of map, where the reduce function checks the results of all your threads and makes the final declaration of "Prime" or "Not Prime". But I think this is over-engineering. Plus, it doesn't fit nicely with the requirement to terminate computation early once a factor is found.

                Qt Doc Search for browsers: forum.qt.io/topic/35616/web-browser-extension-for-improved-doc-searches

                JonBJ 1 Reply Last reply
                1
                • JKSHJ JKSH

                  Back to using Qt Concurrent for your algorithm:

                  @JonB said in Is QtConcurrent suitable for this concurrent/parallel algorithm?:

                  Among all the QtConcurrent methods for filtering/mapping/reducing I have not figured whether/how this algorithmic behaviour could be executed as stated? (Btw, any solution which creates a list of all the numbers from 2 to limit and then filters/reduces them takes too much space by definition

                  Instead of a list of all numbers to test, generate a list of ranges ("arithmetic progressions" in maths parlance) to test:

                  struct Range {
                      long start;
                      long limit;
                      long step;
                  };
                  

                  Then, apply QtConcurrent::map() to your QList<Range>. This is the idiomatic way^ to express your algorithm above (both are equivalent).

                  BTW, it's pointless/wasteful to test even divisors (except 2). Just check if your number is divisible by 2 before starting any threads, and then let your threads only test odd divisors. This halves the maximum number of divisors that they need to test.

                  Obviously I then need (a) some mechanism of knowing when all threads have run their own loops to completion and never found a divisor for the dividend (so tested number is indeed prime) and (b) a way of a thread returning or signalling immediately when it has found a divisor (tested number is composite)

                  QFutureWatcher is your friend.

                  so that main code can then immediately terminate the other threads and return false.

                  Once an instance of your threadFunc() starts running, it normally can't be terminated before completion (in contrast, QThread offers a terminate() function). So, you'll need to add some kind of escape hatch, like a global Boolean flag that your for-loops check before performing a division. I'm not sure how much overhead this adds -- benchmark and see.

                  @JonB said in Is QtConcurrent suitable for this concurrent/parallel algorithm?:

                  (Looks like QtConcurrent::run() in Run With Promise mode?)

                  Is this the (only/right) way to use QtConcurrent for my proposed algorithm?

                  As you've seen above, Run, Map, and Filter are all viable. I'd imagine that the global Boolean flag has equal or lower overhead than cancelling a Run via a QPromise. However, the latter is more "self-contained" so you could run your prime-check algorithm on multiple dividends simultaneously.

                  ^ P.S. To be extra idiomatic, you could use map-reduce instead of map, where the reduce function checks the results of all your threads and makes the final declaration of "Prime" or "Not Prime". But I think this is over-engineering. Plus, it doesn't fit nicely with the requirement to terminate computation early once a factor is found.

                  JonBJ Offline
                  JonBJ Offline
                  JonB
                  wrote last edited by
                  #8

                  @JKSH said in Is QtConcurrent suitable for this concurrent/parallel algorithm?:

                  Then, apply QtConcurrent::map() to your QList<Range>. This is the idiomatic way^ to express your algorithm above.

                  We shall see. For now I am creating QtConcurrent::run()s for each available core, per my approach earlier, passing in parameters to each one for what "range" to test. No map/filter/reduce, no ranges as ranges. When that's working/timed, I might compare against the list approach. What we are all agreeing, apparently, is not to do the naïve "create a thread for each test division (not with a loop to test a bunch of them)", and let QtConcurrent figure out threads & pool for it. Which to me is the "logical" way a noob might approach it, but I assume grossly slow.

                  QFutureWatcher is your friend.

                  Ah, OK.

                  Once an instance of your threadFunc() starts running, it normally can't be terminated before completion (in contrast, QThread offers a terminate() function). So, you'll need to add some kind of escape hatch, like a global Boolean flag that your for-loops check

                  Ah, OK again. Yes, surprises me if basic QThread does allow forceful terminate.

                  BTW, it's pointless/wasteful to test even divisors (except 2). Just check if your number is divisible by 2 before starting any threads, and then let your threads only test odd divisors. This halves the maximum number of divisors that they need to test.

                  Of course, already in code but not shown earlier.

                  1 Reply Last reply
                  0
                  • S Offline
                    S Offline
                    SimonSchroeder
                    wrote last edited by
                    #9

                    Well, maybe a small side quest: The easiest method to make the algorithm parallel might be using OpenMP:

                    long dividend = ...;  // the number to test for primality
                    long limit = sqrt(test);
                    bool isPrime = false;
                    #pragma omp parallel for shared(limit,dividend,divisor,isPrime)
                    for (long divisor = 2; divisor <= limit; divisor++)
                        if (dividend % divisor == 0)
                            isPrime = true;
                    return  isPrime;
                    

                    You have to turn on OpenMP (something like -fopenmp) to make it run in parallel. OpenMP automatically scales with the number of cores and you can even choose a scheduling algorithm and chunk size to optimize it further. Quick testing can be done by setting OpenMP environment variables to change the defaults.

                    The equivalent in Qt is actually QtConcurrent. QtConcurrent::run doesn't really help because most of the work is still on your shoulders, but map-and-reduce is perfect for this. QtConcurrent actually starts a thread pool and does not oversubscribe your CPU cores (even with QtConcurrent::run). It is also not constantly starting new threads, but reuses threads from the thread pool. You can do similar things with QThread if you just start the event loop for each worker thread and push "tasks" by using QMetaObject::invokeMethod.

                    You might have noticed that for the OpenMP example I did not include early termination (yet). IIRC with OpenMP this is a little bit more complicated and you might have to write continue to run empty loops for "early terminations". Usually (independent of OpenMP), it is sufficient to have a boolean variable accessible to all threads and let one thread toggle it if it finds a solution. It does not have to be atomic. For early termination you probably don't care if termination is immediate or slightly delayed (until the cores have synched up). At least, you don't have to pay for the overhead of an atomic in every single loop iteration.

                    If you are looking for a fast solution, you can get some inspiration from Dave Plummer's Prime Drag Race. Dave Plummer is a retired Microsoft engineer on YouTube. In the Prime Drag Race people competed/compete with different programming languages to write the fastest program.

                    1 Reply Last reply
                    0

                    • Login

                    • Login or register to search.
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved