Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. General talk
  3. Brainstorm
  4. How many emitted signals is too many?
Forum Updated to NodeBB v4.3 + New Features

How many emitted signals is too many?

Scheduled Pinned Locked Moved Unsolved Brainstorm
23 Posts 5 Posters 468 Views 4 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.
  • jeremy_kJ Online
    jeremy_kJ Online
    jeremy_k
    wrote last edited by
    #2

    That does seem like a lot of events. Have you considered a semaphore to indicate that updates are available, and a separate queue or atomic pointer mailbox transfer data?

    Does the UI care about seeing every update?

    Asking a question about code? http://eel.is/iso-c++/testcase/

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

      I have worked on various queuing and amalgamating (is that the right word?) of signals received at UI side to cut down on how much UI work needs to be done (varies from displaying a changed value in a simple spinbox/label to displaying areas of memory changed). This has not made as much difference as I expected (managing the queuing and checking for discarding subsumed events has an overhead itself). But that is not really the question I am musing upon here.

      Take the case --- because it separates producer signal from consumer slot --- of the emulator running in its own thread. The UI connects to its signals with queued connection. Now you can forget about what the UI slots do with the signals and how optimized or unoptimized it is. The issue I see is that the emulator thread emits hundreds or thousands or millions of signals quickly as it runs. If the UI thread does not happen to process these very promptly --- and since we are multi-threaded we have no idea when that might be --- all the signals are queued by Qt, somehow and somewhere. Right? Which I don't like, there could be "a lot". I am a purist, this is not a commercial venture, so maybe I am fussy about finding a solution which "seems right".

      So the issue I am worrying about at the moment is the emulator issuing too many signals, as yet not pulled from the internal queue, rather than what consumers do with them later. I don't feel I am emitting too many or non-useful ones: I have, say, one for each of a handful of registers changed and one for any memory write. That's about it. They are emitted because they seem logical and useful. (The UI will ultimately use these to cause update of corresponding widgets.) The issue arises because the emulator can, obviously, do a lot of register/memory updates!

      jeremy_kJ 1 Reply Last reply
      0
      • JonBJ JonB

        I have worked on various queuing and amalgamating (is that the right word?) of signals received at UI side to cut down on how much UI work needs to be done (varies from displaying a changed value in a simple spinbox/label to displaying areas of memory changed). This has not made as much difference as I expected (managing the queuing and checking for discarding subsumed events has an overhead itself). But that is not really the question I am musing upon here.

        Take the case --- because it separates producer signal from consumer slot --- of the emulator running in its own thread. The UI connects to its signals with queued connection. Now you can forget about what the UI slots do with the signals and how optimized or unoptimized it is. The issue I see is that the emulator thread emits hundreds or thousands or millions of signals quickly as it runs. If the UI thread does not happen to process these very promptly --- and since we are multi-threaded we have no idea when that might be --- all the signals are queued by Qt, somehow and somewhere. Right? Which I don't like, there could be "a lot". I am a purist, this is not a commercial venture, so maybe I am fussy about finding a solution which "seems right".

        So the issue I am worrying about at the moment is the emulator issuing too many signals, as yet not pulled from the internal queue, rather than what consumers do with them later. I don't feel I am emitting too many or non-useful ones: I have, say, one for each of a handful of registers changed and one for any memory write. That's about it. They are emitted because they seem logical and useful. (The UI will ultimately use these to cause update of corresponding widgets.) The issue arises because the emulator can, obviously, do a lot of register/memory updates!

        jeremy_kJ Online
        jeremy_kJ Online
        jeremy_k
        wrote last edited by jeremy_k
        #4

        @JonB said in How many emitted signals is too many?:

        I have worked on various queuing and amalgamating (is that the right word?)

        Is this done on the sender side to reduce the signal emissions, or on the receiver side?

        Take the case --- because it separates producer signal from consumer slot --- of the emulator running in its own thread. The UI connects to its signals with queued connection. Now you can forget about what the UI slots do with the signals and how optimized or unoptimized it is. The issue I see is that the emulator thread emits hundreds or thousands or millions of signals quickly as it runs. If the UI thread does not happen to process these very promptly --- and since we are multi-threaded we have no idea when that might be --- all the signals are queued by Qt, somehow and somewhere. Right?

        Yes. Each signal emission for a queued connection will post a QEvent::MetaCall event to the receiver's event queue.

        #include <QCoreApplication>
        #include <QDebug>
        
        struct Filter : public QObject {
            bool eventFilter(QObject *watched, QEvent *event) {
                if (event->type() == QEvent::MetaCall)
                    qDebug() << "MetaCall";
                return false;
            }
        };
        
        int main(int argc, char *argv[])
        {
            QCoreApplication a(argc, argv);
            Filter filter;
            a.installEventFilter(&filter);
            QObject::connect(&a, &QCoreApplication::applicationNameChanged,
                             &a, []() { qDebug() << "application name change";},
                             Qt::ConnectionType::QueuedConnection); // Try changing to DirectConnection
            a.setApplicationName("demo");
        
            return a.exec();
        }
        

        So the issue I am worrying about at the moment is the emulator issuing too many signals, as yet not pulled from the internal queue, rather than what consumers do with them later. I don't feel I am emitting too many or non-useful ones: I have, say, one for each of a handful of registers changed and one for any memory write. That's about it. They are emitted because they seem logical and useful. (The UI will ultimately use these to cause update of corresponding widgets.) The issue arises because the emulator can, obviously, do a lot of register/memory updates!

        Yes, understood. The mismatch between sender and receiver speeds is why I suggest a semaphore (metaphorical, not necessarily a QSemaphore or std::*_semaphore), and a mailbox that the receiver can check and empty at its leisure. The data for events will still be stored until removal, but the presence of data doesn't need to be more than a binary flag. A file containing a trace of the emulator execution is another (slow) option.

        Asking a question about code? http://eel.is/iso-c++/testcase/

        JonBJ 1 Reply Last reply
        0
        • jeremy_kJ jeremy_k

          @JonB said in How many emitted signals is too many?:

          I have worked on various queuing and amalgamating (is that the right word?)

          Is this done on the sender side to reduce the signal emissions, or on the receiver side?

          Take the case --- because it separates producer signal from consumer slot --- of the emulator running in its own thread. The UI connects to its signals with queued connection. Now you can forget about what the UI slots do with the signals and how optimized or unoptimized it is. The issue I see is that the emulator thread emits hundreds or thousands or millions of signals quickly as it runs. If the UI thread does not happen to process these very promptly --- and since we are multi-threaded we have no idea when that might be --- all the signals are queued by Qt, somehow and somewhere. Right?

          Yes. Each signal emission for a queued connection will post a QEvent::MetaCall event to the receiver's event queue.

          #include <QCoreApplication>
          #include <QDebug>
          
          struct Filter : public QObject {
              bool eventFilter(QObject *watched, QEvent *event) {
                  if (event->type() == QEvent::MetaCall)
                      qDebug() << "MetaCall";
                  return false;
              }
          };
          
          int main(int argc, char *argv[])
          {
              QCoreApplication a(argc, argv);
              Filter filter;
              a.installEventFilter(&filter);
              QObject::connect(&a, &QCoreApplication::applicationNameChanged,
                               &a, []() { qDebug() << "application name change";},
                               Qt::ConnectionType::QueuedConnection); // Try changing to DirectConnection
              a.setApplicationName("demo");
          
              return a.exec();
          }
          

          So the issue I am worrying about at the moment is the emulator issuing too many signals, as yet not pulled from the internal queue, rather than what consumers do with them later. I don't feel I am emitting too many or non-useful ones: I have, say, one for each of a handful of registers changed and one for any memory write. That's about it. They are emitted because they seem logical and useful. (The UI will ultimately use these to cause update of corresponding widgets.) The issue arises because the emulator can, obviously, do a lot of register/memory updates!

          Yes, understood. The mismatch between sender and receiver speeds is why I suggest a semaphore (metaphorical, not necessarily a QSemaphore or std::*_semaphore), and a mailbox that the receiver can check and empty at its leisure. The data for events will still be stored until removal, but the presence of data doesn't need to be more than a binary flag. A file containing a trace of the emulator execution is another (slow) option.

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote last edited by JonB
          #5

          @jeremy_k said in How many emitted signals is too many?:

          Is this done on the sender side to reduce the signal emissions, or on the receiver side?

          This has been done on the receiver side. Mainly because it only seemed "logical" to me that the receiver would decide what it wanted or not wanted to do with the signals. But now I am brainstorming that I don't like so many signals being emitted and hence potentially sitting in a queue in memory, so turning my attention to the sender side, reluctantly.

          Which then leads to the other reason. I expect the sender side code to be fast. That means I don't want to spend much time there deciding whether to emit a signal or doing the code to figure how to "merge". Remember that the signal is being emitted, in whatever fashion even if merging/queuing, probably more than once per instruction being executed.

          I love this sort of thing, can't help keep trying to think it through :)

          Yes, understood. The mismatch between sender and receiver speeds is why I suggest a semaphore (metaphorical, not necessarily a QSemaphore or std::*_semaphore), and a mailbox that the receiver can check and empty at its leisure. The data for events will still be stored until removal, but the presence of data doesn't need to be more than a binary flag. A file containing a trace of the emulator execution is another (slow) option.

          Blimey, I think you added this! Not something I'm familiar with (I know about semaphores, not what you are saying about mailboxes or how the data relating to signal would be one bit). Do you have a link or a search term? Oh, I think you just mean a bit to indicate some data, the data is still queued somewhere. Then I don't see what that offers over the present situation where this must be stored in the Qt event queue? I object to accumulating hundreds of thousands of events in a memory queue which are going unprocessed for a period of time and are likely to end up largely ignored at the UI side eventually.

          jeremy_kJ 2 Replies Last reply
          0
          • JonBJ JonB

            @jeremy_k said in How many emitted signals is too many?:

            Is this done on the sender side to reduce the signal emissions, or on the receiver side?

            This has been done on the receiver side. Mainly because it only seemed "logical" to me that the receiver would decide what it wanted or not wanted to do with the signals. But now I am brainstorming that I don't like so many signals being emitted and hence potentially sitting in a queue in memory, so turning my attention to the sender side, reluctantly.

            Which then leads to the other reason. I expect the sender side code to be fast. That means I don't want to spend much time there deciding whether to emit a signal or doing the code to figure how to "merge". Remember that the signal is being emitted, in whatever fashion even if merging/queuing, probably more than once per instruction being executed.

            I love this sort of thing, can't help keep trying to think it through :)

            Yes, understood. The mismatch between sender and receiver speeds is why I suggest a semaphore (metaphorical, not necessarily a QSemaphore or std::*_semaphore), and a mailbox that the receiver can check and empty at its leisure. The data for events will still be stored until removal, but the presence of data doesn't need to be more than a binary flag. A file containing a trace of the emulator execution is another (slow) option.

            Blimey, I think you added this! Not something I'm familiar with (I know about semaphores, not what you are saying about mailboxes or how the data relating to signal would be one bit). Do you have a link or a search term? Oh, I think you just mean a bit to indicate some data, the data is still queued somewhere. Then I don't see what that offers over the present situation where this must be stored in the Qt event queue? I object to accumulating hundreds of thousands of events in a memory queue which are going unprocessed for a period of time and are likely to end up largely ignored at the UI side eventually.

            jeremy_kJ Online
            jeremy_kJ Online
            jeremy_k
            wrote last edited by jeremy_k
            #6

            @JonB said in How many emitted signals is too many?:

            @jeremy_k said in How many emitted signals is too many?:

            Is this done on the sender side to reduce the signal emissions, or on the receiver side?

            This has been done on the receiver side. Mainly because it only seemed "logical" to me that the receiver would decide what it wanted or not wanted to do with the signals. But now I am brainstorming that I don't like so many signals being emitted and hence potentially sitting in a queue in memory, so turning my attention to the sender side, reluctantly.

            Let's stop contemplating the potential existence of a queue in memory. It's there. Modify the example above to change the application name twice if a demonstration is required.

            Which then leads to the other reason. I expect the sender side code to be fast. That means I don't want to spend much time there deciding whether to emit a signal or doing the code to figure how to "merge". Remember that the signal is being emitted, in whatever fashion even if merging/queuing, probably more than once per instruction being executed.

            Set the semaphore from the sender every time the code currently emits a signal. It's unlikely to be any slower, as posting to the event queue also involves synchronization. If that's still too slow, consider doing some compression on the sender side, such as collecting several changes before toggling the semaphore. Perhaps a (lock free?) ring buffer is appropriate to limit the size of queued data.

            Asking a question about code? http://eel.is/iso-c++/testcase/

            JonBJ 1 Reply Last reply
            0
            • jeremy_kJ jeremy_k

              @JonB said in How many emitted signals is too many?:

              @jeremy_k said in How many emitted signals is too many?:

              Is this done on the sender side to reduce the signal emissions, or on the receiver side?

              This has been done on the receiver side. Mainly because it only seemed "logical" to me that the receiver would decide what it wanted or not wanted to do with the signals. But now I am brainstorming that I don't like so many signals being emitted and hence potentially sitting in a queue in memory, so turning my attention to the sender side, reluctantly.

              Let's stop contemplating the potential existence of a queue in memory. It's there. Modify the example above to change the application name twice if a demonstration is required.

              Which then leads to the other reason. I expect the sender side code to be fast. That means I don't want to spend much time there deciding whether to emit a signal or doing the code to figure how to "merge". Remember that the signal is being emitted, in whatever fashion even if merging/queuing, probably more than once per instruction being executed.

              Set the semaphore from the sender every time the code currently emits a signal. It's unlikely to be any slower, as posting to the event queue also involves synchronization. If that's still too slow, consider doing some compression on the sender side, such as collecting several changes before toggling the semaphore. Perhaps a (lock free?) ring buffer is appropriate to limit the size of queued data.

              JonBJ Offline
              JonBJ Offline
              JonB
              wrote last edited by
              #7

              @jeremy_k
              I think I need to think about reducing the number of signals out of the emulator. The sort of thing I do for "reduction" is: say register A emits a signal when it is written to. That emits a signal, the UI will want this to update the visual. But then the register is written to again shortly afterwards, emitting a signal with a new value. The UI won't need to update to the first value if it's going to process the next signal and update to the second value. So here a "signal queuer-reducer" can throw away the first signal and only deal with the second. I have been doing that at receiver side, where it seemed more logical, but I think I need to do some at the emitter side, provided I can keep the logic fast.

              1 Reply Last reply
              0
              • JonBJ JonB

                @jeremy_k said in How many emitted signals is too many?:

                Is this done on the sender side to reduce the signal emissions, or on the receiver side?

                This has been done on the receiver side. Mainly because it only seemed "logical" to me that the receiver would decide what it wanted or not wanted to do with the signals. But now I am brainstorming that I don't like so many signals being emitted and hence potentially sitting in a queue in memory, so turning my attention to the sender side, reluctantly.

                Which then leads to the other reason. I expect the sender side code to be fast. That means I don't want to spend much time there deciding whether to emit a signal or doing the code to figure how to "merge". Remember that the signal is being emitted, in whatever fashion even if merging/queuing, probably more than once per instruction being executed.

                I love this sort of thing, can't help keep trying to think it through :)

                Yes, understood. The mismatch between sender and receiver speeds is why I suggest a semaphore (metaphorical, not necessarily a QSemaphore or std::*_semaphore), and a mailbox that the receiver can check and empty at its leisure. The data for events will still be stored until removal, but the presence of data doesn't need to be more than a binary flag. A file containing a trace of the emulator execution is another (slow) option.

                Blimey, I think you added this! Not something I'm familiar with (I know about semaphores, not what you are saying about mailboxes or how the data relating to signal would be one bit). Do you have a link or a search term? Oh, I think you just mean a bit to indicate some data, the data is still queued somewhere. Then I don't see what that offers over the present situation where this must be stored in the Qt event queue? I object to accumulating hundreds of thousands of events in a memory queue which are going unprocessed for a period of time and are likely to end up largely ignored at the UI side eventually.

                jeremy_kJ Online
                jeremy_kJ Online
                jeremy_k
                wrote last edited by
                #8

                @JonB said in How many emitted signals is too many?:

                Blimey, I think you added this! Not something I'm familiar with (I know about semaphores, not what you are saying about mailboxes or how the data relating to signal would be one bit). Do you have a link or a search term?

                Too quick editing on my part merged some sections.

                These concepts show up in operating system theory. https://wiki.osdev.org/Message_Passing looks like a reasonable place to start. There's a lifetime supply of text books that go into more detail. Without suggesting a semester or two studying Minix, I don't have a good suggestion.

                Asking a question about code? http://eel.is/iso-c++/testcase/

                JonBJ 1 Reply Last reply
                0
                • jeremy_kJ jeremy_k

                  @JonB said in How many emitted signals is too many?:

                  Blimey, I think you added this! Not something I'm familiar with (I know about semaphores, not what you are saying about mailboxes or how the data relating to signal would be one bit). Do you have a link or a search term?

                  Too quick editing on my part merged some sections.

                  These concepts show up in operating system theory. https://wiki.osdev.org/Message_Passing looks like a reasonable place to start. There's a lifetime supply of text books that go into more detail. Without suggesting a semester or two studying Minix, I don't have a good suggestion.

                  JonBJ Offline
                  JonBJ Offline
                  JonB
                  wrote last edited by
                  #9

                  @jeremy_k Thanks I'll read through the link tomorrow :) Maybe after I've thinned the signals coming out of the emulator... ;-)

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

                    Well I made one change: although conceptually each processor flag update should cause a signal, so that the UI can show it, most instructions set multiple flags. Moved to one signal at end of instruction for all flags instead and that took tens of thousands of signals out. Improved some of the "merging" of multiple signals to make that quicker. All good, clean fun :)

                    1 Reply Last reply
                    0
                    • SGaistS Offline
                      SGaistS Offline
                      SGaist
                      Lifetime Qt Champion
                      wrote last edited by
                      #11

                      Hi,

                      I think you might be looking for "signal compression".

                      This StackOverflow answer does a good job explaining it.

                      Interested in AI ? www.idiap.ch
                      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                      JonBJ 1 Reply Last reply
                      3
                      • SGaistS SGaist

                        Hi,

                        I think you might be looking for "signal compression".

                        This StackOverflow answer does a good job explaining it.

                        JonBJ Offline
                        JonBJ Offline
                        JonB
                        wrote last edited by
                        #12

                        @SGaist
                        On first reading at least:

                        The event contains the sender object, the signal id, the slot index, and packaged call parameters.

                        The objective is to compress such calls so that only one unique call exists in the event queue for a given tuple of (sender object, sender signal, receiver object, receiver slot).

                        I don't see how the second quotation deals with the first's "and packaged call parameters"? My signals have parameters. And sometimes they matter, sometimes they don't: some of my signals a later one completely overwrites an earlier one, like setting the value of a register, while others do not, like you can't reduce multiple dataChanged() signals to one. If the proposed code when "pruning" either ignores parameters or only matches on identical ones I don't see how that suffices.

                        1 Reply Last reply
                        0
                        • jeremy_kJ Online
                          jeremy_kJ Online
                          jeremy_k
                          wrote last edited by jeremy_k
                          #13

                          Here's a demonstration of the mailbox idea I alluded to, using an atomic pointer to post the latest state. At most, there are 3 live versions at a time: 1 in progress, 1 in the mailbox, and 1 being processed in the main thread.

                          Edit: I should have referred to this pattern as triple buffering

                          #include <QCoreApplication>
                          #include <QThread>
                          #include <QDebug>
                          #include <QTimer>
                          
                          struct data {
                              int count;
                          };
                          
                          struct Sender : public QThread {
                              QAtomicPointer<data> & m_mailbox;
                              Sender(QAtomicPointer<data> & mailbox, QObject *parent=nullptr)
                                  : QThread(parent), m_mailbox(mailbox) {}
                          
                              void run() override {
                                  data *next = nullptr;
                                  int counter = 0;
                                  while (!isInterruptionRequested()) {
                                      if (!next) {
                                          next = new data;
                                      }
                                      next->count = counter++;
                                      next = m_mailbox.fetchAndStoreOrdered(next);
                                  }
                              }
                          };
                          
                          int main(int argc, char *argv[])
                          {
                              QCoreApplication a(argc, argv);
                              QAtomicPointer<data> mailbox;
                              Sender s(mailbox);
                              s.start();
                          
                              QTimer t;
                              t.setInterval(1000);
                              QObject::connect(&t, &QTimer::timeout, [&]() {
                                  data *next = mailbox.fetchAndStoreOrdered(nullptr);
                                  if (!next)
                                      return;
                                  qDebug() << "acquired new value" << next->count;
                                  delete next;
                              });
                              t.start();
                          
                              a.exec();
                          }
                          

                          Asking a question about code? http://eel.is/iso-c++/testcase/

                          JonBJ 1 Reply Last reply
                          1
                          • S Offline
                            S Offline
                            SimonSchroeder
                            wrote last edited by
                            #14

                            This problem reminds me of progress dialogs: If you have a fast loop and update the progress bar/progress dialog in each iteration the GUI will hang. The same thing will happen in your case and for a good user experience you should absolutely do something about that. Here are a few ideas (some derived from earlier posts):

                            1. You mentioned the "signal queuer-reducer". Instead of putting it into the sender or receiver thread, you could have a third thread running. This third thread could be tasked with just recuding the number of actual signals send to the intended receiver. This would leave enough time for both the emulator thread and the GUI thread.

                            2. I'm not sure how the compressEvent() is supposed to work. I do know that update() first collects several calls. I'm not sure how to google for this (and I believe it has gotten harder to find this), but there is an established approach using two timers: The first timer is to delay firing the actual signal just in case the same signal comes in again. Everytime the same signal comes in, the timer is reset. This just adds a tiny delay to the update after the last signal was emitted. Maybe you could choose something like 5 or 10ms for this first timer. The problem, however, is that in your specific case the signal might never be emitted because the first timer is always reset. That's where the second timer comes in: It has a longer timeout and is triggered with the first signal. I can be something like 50ms. On timeout it emits the last signal. This makes sure that occasionally something happens. Here are some of the important functions for a class that I have:

                            void ConsolidatedSlotCall::timeout()
                            {
                                minTimer.stop();
                                maxTimer.stop();
                                if(function && parameterPack)
                                {
                                    function->callWithParameters(parameterPack);
                                    emit slotTriggered();
                                }
                            }
                            
                            void ConsolidatedSlotCall::delayCall()
                            {
                                updateTimers();
                            }
                            
                            void ConsolidatedSlotCall::initTimers()
                            {
                                minTimer.setInterval(5);
                                maxTimer.setInterval(25);
                            
                                QObject::connect(&minTimer, &QTimer::timeout, this, &ConsolidatedSlotCall::timeout);
                                QObject::connect(&maxTimer, &QTimer::timeout, this, &ConsolidatedSlotCall::timeout);
                            }
                            
                            void ConsolidatedSlotCall::updateTimers()
                            {
                                QMetaObject::invokeMethod(&minTimer,[this]()    // make sure timers are started/stopped inside the correct thread
                                {
                                    if (!maxTimer.isActive())
                                    {
                                        maxTimer.start();
                                    }
                            
                                    minTimer.stop();
                                    minTimer.start();
                                });
                            }
                            

                            The full implementation is a lot more involved. There is some type erasure going on and the parameters have to be saved and finally applied when the signal gets emitted. (You see function and parameterPack in the first method I posted.) If you want I can post some more code for this solution.

                            1. This is the approach I use most often for progress updates: I'm using QTime and check if 50ms have elapsed and only after this I actually update the progress. This would be done on the senders side and I don't think it takes a lot of time. It used to be quite nice because there were member functions elapsed() and restart(). These are not available in Qt 6 anymore. You have to use msecsTo() and QTime::currentTime() instead. I guess that in your case you would have a separate QTime for each kind of signal you can emit. This would throttle each signal individually. This approach is actually the easiest to implement and plenty fast.
                            JonBJ 1 Reply Last reply
                            1
                            • jeremy_kJ jeremy_k

                              Here's a demonstration of the mailbox idea I alluded to, using an atomic pointer to post the latest state. At most, there are 3 live versions at a time: 1 in progress, 1 in the mailbox, and 1 being processed in the main thread.

                              Edit: I should have referred to this pattern as triple buffering

                              #include <QCoreApplication>
                              #include <QThread>
                              #include <QDebug>
                              #include <QTimer>
                              
                              struct data {
                                  int count;
                              };
                              
                              struct Sender : public QThread {
                                  QAtomicPointer<data> & m_mailbox;
                                  Sender(QAtomicPointer<data> & mailbox, QObject *parent=nullptr)
                                      : QThread(parent), m_mailbox(mailbox) {}
                              
                                  void run() override {
                                      data *next = nullptr;
                                      int counter = 0;
                                      while (!isInterruptionRequested()) {
                                          if (!next) {
                                              next = new data;
                                          }
                                          next->count = counter++;
                                          next = m_mailbox.fetchAndStoreOrdered(next);
                                      }
                                  }
                              };
                              
                              int main(int argc, char *argv[])
                              {
                                  QCoreApplication a(argc, argv);
                                  QAtomicPointer<data> mailbox;
                                  Sender s(mailbox);
                                  s.start();
                              
                                  QTimer t;
                                  t.setInterval(1000);
                                  QObject::connect(&t, &QTimer::timeout, [&]() {
                                      data *next = mailbox.fetchAndStoreOrdered(nullptr);
                                      if (!next)
                                          return;
                                      qDebug() << "acquired new value" << next->count;
                                      delete next;
                                  });
                                  t.start();
                              
                                  a.exec();
                              }
                              
                              JonBJ Offline
                              JonBJ Offline
                              JonB
                              wrote last edited by JonB
                              #15

                              @jeremy_k
                              Hi Jeremy, thanks for this. So that I understand; this essentially allows a single updated data-message to be passed from producer to consumer. The atomic stuff allows for safe read/write across thread boundaries. The consumer uses sleep + poll to read.

                              And the point, compared to sending signals, is that we don't end up with a stream of unprocessed signals sitting around taking up memory until they are pulled at consumer side.

                              I am thinking aloud about this. I am not keen on the consumer side doing sleep & poll. I would like consumer to see message/data as soon as it is available and consumer thread is running (unknown time after producer thread was running and posted data) while at same time not sleeping or polling. A signal does that for me. I don't fancy a QTimer(0) for checking as that is too busy, as is QTimer(1), while QTimer(1000) is not frequent enough. But the problem with signals alone is that there can be too many of them in the event queue.

                              I think I could adapt your idea of "sharing" data between producer and consumer without making consumer issue a cross-thread request for latest data along the lines of:

                              1. Producer uses your code to store data first time in mailbox.
                              2. At this point it sends a signal "data ready". And notes that it has sent signal.
                              3. Producer produces new data.
                              4. It replaces state in mailbox, but seeing that signal has been sent it does not send another signal.
                              5. Consumer at some point runs and receives "data ready" signal.
                              6. At that point it reads mailbox and lets consumer know it has done so. In your code by storing nullptr back to mailbox data.
                              7. Next time producer has data to send it sees that consumer has read previous data and so additionally sends new "data ready" signal, and we are back at step #1.

                              The essential of what we are doing here is changing producer Qt signals which currently contain data over to signals with no data parameters and storing the data in the shared mailbox instead. Which allows us to reduce to just a single signal from producer per consumer read of that signal, with data elsewhere but immediately accessible to consumer without having to query producer. That is interesting. I will think about this :)

                              jeremy_kJ 1 Reply Last reply
                              0
                              • S SimonSchroeder

                                This problem reminds me of progress dialogs: If you have a fast loop and update the progress bar/progress dialog in each iteration the GUI will hang. The same thing will happen in your case and for a good user experience you should absolutely do something about that. Here are a few ideas (some derived from earlier posts):

                                1. You mentioned the "signal queuer-reducer". Instead of putting it into the sender or receiver thread, you could have a third thread running. This third thread could be tasked with just recuding the number of actual signals send to the intended receiver. This would leave enough time for both the emulator thread and the GUI thread.

                                2. I'm not sure how the compressEvent() is supposed to work. I do know that update() first collects several calls. I'm not sure how to google for this (and I believe it has gotten harder to find this), but there is an established approach using two timers: The first timer is to delay firing the actual signal just in case the same signal comes in again. Everytime the same signal comes in, the timer is reset. This just adds a tiny delay to the update after the last signal was emitted. Maybe you could choose something like 5 or 10ms for this first timer. The problem, however, is that in your specific case the signal might never be emitted because the first timer is always reset. That's where the second timer comes in: It has a longer timeout and is triggered with the first signal. I can be something like 50ms. On timeout it emits the last signal. This makes sure that occasionally something happens. Here are some of the important functions for a class that I have:

                                void ConsolidatedSlotCall::timeout()
                                {
                                    minTimer.stop();
                                    maxTimer.stop();
                                    if(function && parameterPack)
                                    {
                                        function->callWithParameters(parameterPack);
                                        emit slotTriggered();
                                    }
                                }
                                
                                void ConsolidatedSlotCall::delayCall()
                                {
                                    updateTimers();
                                }
                                
                                void ConsolidatedSlotCall::initTimers()
                                {
                                    minTimer.setInterval(5);
                                    maxTimer.setInterval(25);
                                
                                    QObject::connect(&minTimer, &QTimer::timeout, this, &ConsolidatedSlotCall::timeout);
                                    QObject::connect(&maxTimer, &QTimer::timeout, this, &ConsolidatedSlotCall::timeout);
                                }
                                
                                void ConsolidatedSlotCall::updateTimers()
                                {
                                    QMetaObject::invokeMethod(&minTimer,[this]()    // make sure timers are started/stopped inside the correct thread
                                    {
                                        if (!maxTimer.isActive())
                                        {
                                            maxTimer.start();
                                        }
                                
                                        minTimer.stop();
                                        minTimer.start();
                                    });
                                }
                                

                                The full implementation is a lot more involved. There is some type erasure going on and the parameters have to be saved and finally applied when the signal gets emitted. (You see function and parameterPack in the first method I posted.) If you want I can post some more code for this solution.

                                1. This is the approach I use most often for progress updates: I'm using QTime and check if 50ms have elapsed and only after this I actually update the progress. This would be done on the senders side and I don't think it takes a lot of time. It used to be quite nice because there were member functions elapsed() and restart(). These are not available in Qt 6 anymore. You have to use msecsTo() and QTime::currentTime() instead. I guess that in your case you would have a separate QTime for each kind of signal you can emit. This would throttle each signal individually. This approach is actually the easiest to implement and plenty fast.
                                JonBJ Offline
                                JonBJ Offline
                                JonB
                                wrote last edited by
                                #16

                                @SimonSchroeder said in How many emitted signals is too many?:

                                You mentioned the "signal queuer-reducer". Instead of putting it into the sender or receiver thread, you could have a third thread running. This third thread could be tasked with just recuding the number of actual signals send to the intended receiver. This would leave enough time for both the emulator thread and the GUI thread.

                                Hi, yes, I had considered this! Apart from the extra code, I "worry" theoretically about timings here. Once we have repeated signals emitted by one thread to be consumed or thinned in any other thread, be that the Ui or a tertiary one, we could have: producer bangs out 100,000 signals before other/tertiary thread happens to be scheduled to pull them and that uses a lot of Qt's event loop memory to store the queue. I don't like that from a "purist" point of view.

                                Have a look at @jeremy_k's suggestion above and my reply. Looks like separating data content (to be single, replaceable, atomically) from signal allows for single signal emission per many changing, overwritable data, so low memory (and processing) overhead.

                                1 Reply Last reply
                                0
                                • J.HilkJ Offline
                                  J.HilkJ Offline
                                  J.Hilk
                                  Moderators
                                  wrote last edited by
                                  #17

                                  I have run into similar situations where a value could update multiple times in short intervals and resulted in a flood of GUI updates.

                                  What I ended up doing was the following:

                                  Each value became its own full fledged property/class: getter, setter, signal and a QTimer object.

                                  When the setter is called and the timer is not running emit the signal directly, and start the timer.
                                  When the setter is called and the timer is running, set a flag to schedule a signal on timer timeout.
                                  In the timeout slot, stop the timer, if the signal is scheduled emit the signal with the latest value as argument and restart timer. If ne emit is scheduled we end up in original state again

                                  Something like this, quick moc up not actual production ;)

                                  #include <QObject>
                                  #include <QVariant>
                                  #include <QTimer>
                                  
                                  class BaseProperty final : public QObject
                                  {
                                      Q_OBJECT
                                      Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
                                      Q_PROPERTY(int intervalMs READ intervalMs WRITE setIntervalMs NOTIFY intervalMsChanged)
                                  
                                  public:
                                      explicit BaseProperty(QObject* parent = nullptr)
                                          : QObject(parent)
                                      {
                                          m_timer.setSingleShot(true);
                                          QObject::connect(&m_timer, &QTimer::timeout,
                                                           this, &BaseProperty::onTimeout);
                                      }
                                  
                                      QVariant value() const { return m_value; }
                                  
                                      int intervalMs() const { return m_intervalMs; }
                                  
                                  public slots:
                                      void setIntervalMs(int ms)
                                      {
                                          if (ms < 0) ms = 0;
                                          if (m_intervalMs == ms) return;
                                          m_intervalMs = ms;
                                          emit intervalMsChanged(m_intervalMs);
                                      }
                                  
                                      void setValue(const QVariant& value)
                                      {
                                          if(value ==  m_value) {
                                                return;
                                           }
                                  
                                          m_value = value;
                                  
                                          if (!m_timer.isActive())  {
                                              emit valueChanged(m_value);
                                              m_timer.start(m_intervalMs);
                                              m_pendingEmit = false;
                                              return;
                                          }
                                  
                                          m_pendingEmit = true;
                                      }
                                  
                                  signals:
                                      void valueChanged(const QVariant& value);
                                      void intervalMsChanged(int intervalMs);
                                  
                                  private slots:
                                      void onTimeout()
                                      {
                                          if (!m_pendingEmit)  {
                                              return;
                                          }
                                  
                                          m_pendingEmit = false;
                                  
                                          emit valueChanged(m_value);
                                  
                                          m_timer.start(m_intervalMs);
                                      }
                                  
                                  private:
                                      QVariant m_value;
                                      QTimer   m_timer;
                                      int      m_intervalMs = 50;
                                      bool     m_pendingEmit = false;
                                  };
                                  
                                  

                                  Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


                                  Q: What's that?
                                  A: It's blue light.
                                  Q: What does it do?
                                  A: It turns blue.

                                  JonBJ 1 Reply Last reply
                                  2
                                  • J.HilkJ J.Hilk

                                    I have run into similar situations where a value could update multiple times in short intervals and resulted in a flood of GUI updates.

                                    What I ended up doing was the following:

                                    Each value became its own full fledged property/class: getter, setter, signal and a QTimer object.

                                    When the setter is called and the timer is not running emit the signal directly, and start the timer.
                                    When the setter is called and the timer is running, set a flag to schedule a signal on timer timeout.
                                    In the timeout slot, stop the timer, if the signal is scheduled emit the signal with the latest value as argument and restart timer. If ne emit is scheduled we end up in original state again

                                    Something like this, quick moc up not actual production ;)

                                    #include <QObject>
                                    #include <QVariant>
                                    #include <QTimer>
                                    
                                    class BaseProperty final : public QObject
                                    {
                                        Q_OBJECT
                                        Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
                                        Q_PROPERTY(int intervalMs READ intervalMs WRITE setIntervalMs NOTIFY intervalMsChanged)
                                    
                                    public:
                                        explicit BaseProperty(QObject* parent = nullptr)
                                            : QObject(parent)
                                        {
                                            m_timer.setSingleShot(true);
                                            QObject::connect(&m_timer, &QTimer::timeout,
                                                             this, &BaseProperty::onTimeout);
                                        }
                                    
                                        QVariant value() const { return m_value; }
                                    
                                        int intervalMs() const { return m_intervalMs; }
                                    
                                    public slots:
                                        void setIntervalMs(int ms)
                                        {
                                            if (ms < 0) ms = 0;
                                            if (m_intervalMs == ms) return;
                                            m_intervalMs = ms;
                                            emit intervalMsChanged(m_intervalMs);
                                        }
                                    
                                        void setValue(const QVariant& value)
                                        {
                                            if(value ==  m_value) {
                                                  return;
                                             }
                                    
                                            m_value = value;
                                    
                                            if (!m_timer.isActive())  {
                                                emit valueChanged(m_value);
                                                m_timer.start(m_intervalMs);
                                                m_pendingEmit = false;
                                                return;
                                            }
                                    
                                            m_pendingEmit = true;
                                        }
                                    
                                    signals:
                                        void valueChanged(const QVariant& value);
                                        void intervalMsChanged(int intervalMs);
                                    
                                    private slots:
                                        void onTimeout()
                                        {
                                            if (!m_pendingEmit)  {
                                                return;
                                            }
                                    
                                            m_pendingEmit = false;
                                    
                                            emit valueChanged(m_value);
                                    
                                            m_timer.start(m_intervalMs);
                                        }
                                    
                                    private:
                                        QVariant m_value;
                                        QTimer   m_timer;
                                        int      m_intervalMs = 50;
                                        bool     m_pendingEmit = false;
                                    };
                                    
                                    
                                    JonBJ Offline
                                    JonBJ Offline
                                    JonB
                                    wrote last edited by
                                    #18

                                    @J.Hilk said in How many emitted signals is too many?:

                                    When the setter is called and the timer is not running emit the signal directly, and start the timer.
                                    When the setter is called and the timer is running, set a flag to schedule a signal on timer timeout.

                                    This means that if the UI gets to run after step 2 it will only see the update from step 1 and not yet from step 2 since that is in a "pending timeout". It will only see the first update when many further ones could be "pending". From my "purist" pov I don't like this, the UI update is not as up-to-date as it can/should be.

                                    1 Reply Last reply
                                    0
                                    • J.HilkJ Offline
                                      J.HilkJ Offline
                                      J.Hilk
                                      Moderators
                                      wrote last edited by
                                      #19

                                      with the approach of passing the value as argument through the queue, yes, the ui may be a few milliseconds behind. You could forgo the value in argument and simply use the signal to tell the gui to call the getter and make that mutex locked. That way it would get the latest value, and the getter call could also stop the timer, so no unnecessary operations happen


                                      Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


                                      Q: What's that?
                                      A: It's blue light.
                                      Q: What does it do?
                                      A: It turns blue.

                                      JonBJ 1 Reply Last reply
                                      0
                                      • J.HilkJ J.Hilk

                                        with the approach of passing the value as argument through the queue, yes, the ui may be a few milliseconds behind. You could forgo the value in argument and simply use the signal to tell the gui to call the getter and make that mutex locked. That way it would get the latest value, and the getter call could also stop the timer, so no unnecessary operations happen

                                        JonBJ Offline
                                        JonBJ Offline
                                        JonB
                                        wrote last edited by
                                        #20

                                        @J.Hilk said in How many emitted signals is too many?:

                                        use the signal to tell the gui to call the getter and make that mutex locked

                                        Yes! But I'd like to avoid the sync point for this. I liked @jeremy_k's suggestion above. When he uses producer:

                                        QAtomicPointer<data> & m_mailbox;
                                        m_mailbox.fetchAndStoreOrdered(next);
                                        

                                        and consumer:

                                        data *next = mailbox.fetchAndStoreOrdered(nullptr);
                                        

                                        is that doing just the same behind the scenes as your explicit "call the getter and make that mutex locked"? Or is it a bit subtler than that?

                                        1 Reply Last reply
                                        0
                                        • JonBJ JonB

                                          @jeremy_k
                                          Hi Jeremy, thanks for this. So that I understand; this essentially allows a single updated data-message to be passed from producer to consumer. The atomic stuff allows for safe read/write across thread boundaries. The consumer uses sleep + poll to read.

                                          And the point, compared to sending signals, is that we don't end up with a stream of unprocessed signals sitting around taking up memory until they are pulled at consumer side.

                                          I am thinking aloud about this. I am not keen on the consumer side doing sleep & poll. I would like consumer to see message/data as soon as it is available and consumer thread is running (unknown time after producer thread was running and posted data) while at same time not sleeping or polling. A signal does that for me. I don't fancy a QTimer(0) for checking as that is too busy, as is QTimer(1), while QTimer(1000) is not frequent enough. But the problem with signals alone is that there can be too many of them in the event queue.

                                          I think I could adapt your idea of "sharing" data between producer and consumer without making consumer issue a cross-thread request for latest data along the lines of:

                                          1. Producer uses your code to store data first time in mailbox.
                                          2. At this point it sends a signal "data ready". And notes that it has sent signal.
                                          3. Producer produces new data.
                                          4. It replaces state in mailbox, but seeing that signal has been sent it does not send another signal.
                                          5. Consumer at some point runs and receives "data ready" signal.
                                          6. At that point it reads mailbox and lets consumer know it has done so. In your code by storing nullptr back to mailbox data.
                                          7. Next time producer has data to send it sees that consumer has read previous data and so additionally sends new "data ready" signal, and we are back at step #1.

                                          The essential of what we are doing here is changing producer Qt signals which currently contain data over to signals with no data parameters and storing the data in the shared mailbox instead. Which allows us to reduce to just a single signal from producer per consumer read of that signal, with data elsewhere but immediately accessible to consumer without having to query producer. That is interesting. I will think about this :)

                                          jeremy_kJ Online
                                          jeremy_kJ Online
                                          jeremy_k
                                          wrote last edited by
                                          #21

                                          @JonB said in How many emitted signals is too many?:

                                          @jeremy_k
                                          Hi Jeremy, thanks for this. So that I understand; this essentially allows a single updated data-message to be passed from producer to consumer. The atomic stuff allows for safe read/write across thread boundaries. The consumer uses sleep + poll to read.
                                          [...]

                                          The essential of what we are doing here is changing producer Qt signals which currently contain data over to signals with no data parameters and storing the data in the shared mailbox instead. Which allows us to reduce to just a single signal from producer per consumer read of that signal, with data elsewhere but immediately accessible to consumer without having to query producer. That is interesting. I will think about this :)

                                          That reads like what I intended to convey. Score 1 for the lowly minimal working example. There are opportunities for improvement depending on the details. Polling at the display frame rate should provide lower display latency. Signaling may improve efficiency in a generally idle system.

                                          Asking a question about code? http://eel.is/iso-c++/testcase/

                                          JonBJ 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