Search code examples
c++performanceqttimerrobotics

Using timers with performance-critical software (Qt)


I am developing an application that is responsible of moving and managing robots over an UDP connection.

The application needs to:

  • Read joystick/user input using SDL.
  • Generate and send a control packet to the robot every 20 milliseconds (UDP)
  • Receive and decode response packets from the robot (~20 msecs). This was implemented with the signal/slot mechanism and does not require a timer.
  • Receive and process robot messages for debugging reasons. This is not time-regulated.
  • Update the UI regularly to keep the user notified about the status of the robot (e.g. battery voltage). For most cases, I have also used Qt's signal/slot mechanism.
  • Use a watchdog that disables the robot if no response is received after 1 second. The watchdog is reset when the application receives a robot packet (~20 msecs)

For the moment, I have implemented all of the above. However, the application fails to send the packets regularly when the watchdog is activated or when two or more QTimer objects are used. The application would generally work, but I would not consider it "production ready". I have tried to use the precision flags of the timers (Qt::Precise, Qt::Coarse and Qt::VeryCoarse), but I still experienced problems.

Notes:

  • The code is generally well organized, there are no "god objects" in the code base (most source files are less than 150 lines long and only create the necessary dependencies).
  • Most of the times, I use QTimer::singleShot() (e.g. I will only send the next packet once the current packet has been sent).

Where we use timers:

  • To read joystick input (~50 msecs, precise timer)
  • To send robot packets (~20 msecs, precise timer)
  • To update some aspects of the UI (~500 msecs, coarse timer)
  • To update the elapsed time since the robot was enabled (~100 msecs, precise timer)
  • To implement a watchdog (put the application and robot in safe state if 1000 msecs have passed without a robot response)
  • Note: the watchdog is feed when we receive a response packet from the robot (~20 msecs)

Do you have any recommendations for using QTimer objects with performance-critical code (any idea is welcome). Note that I have also tried to use different threads, but it has caused me more problems, since the application would not be in "sync", thus failing to effectively control the robots that we have tested.


Solution

  • Actually, I seem to have underestimated Qt's timer and event loop performance. On my system I get on average around 20k nanoseconds for an event loop cycle plus the overhead from scheduling a queued function call, and a timer with interval 1 millisecond is rarely late, most of the timeouts are a few thousand nanoseconds short of a millisecond. But it is a high end system, on embedded hardware it may be a lot worse.

    You should take the time and profile your target system and Qt build to determine whether it can indeed run snappy enough, and based on those measurements, adjust your timings to compensate for the system delays to get your events scheduled more on time.

    You should definitely keep the timer thread as free as possible, because if you block it by IO or extensive computation, your timer will not be accurate. Use a dedicated thread to schedule work and extra worker threads to do the actual work. You may also try playing with thread priorities a bit.

    Worst case scenario, look for 3rd party high performance event loop implementations or create your own and potentially, also a faster signaling mechanism as ell. As I already mentioned in the comments, Qt's inter-thread queued signals are very slow, at least compared to something like indirect function calls.

    Last but not least, if you want to do task X every N units of time, it will only be only possible if task X takes N units of time or less on your system. You need to make this consideration for each task, and for all tasks running concurrently. And in order to get accurate scheduling, you should measure how long did task X took, and if less than its frequency, schedule the next execution in the time remaining, otherwise execute immediately.