Reputation: 4509
Running on Windows7 64bit machine with a very powerful CPU (8 core 16 threads). I used a QTimer to trigger a function call at 50Hz. But I ended up with 30Hz. The function call itself definitely takes less than 10ms to finish. The whole process happens in a separate thread.
What could go wrong in my case? The Qt's doc said it will be accurate within 5%?
Upvotes: 8
Views: 5685
Reputation: 14355
Just to illustrate the issue, I did some quick-and-dirty timing in PyQt5
on a Windows 7 64-bit system, using QTimer
both with and without the high-precision setting.
Here's what the result looks like using default precision (as in PyQt4):
Dots represent the median
of 100 samples, vertical lines indicate the range (min
to max
). Ideally, all dots would be on the dashed line.
Note the obvious discrepancies between approx. 20ms and 100ms. For example, setting QTimer
to 20ms
(50Hz
) in my case results in an actual (measured) interval of approx. 31ms (32Hz
). Coarse indeed.
And this is what it looks like when using the PreciseTimer
:
Much better, I would say, especially if you are interested in intervals between 20ms and 100 ms.
For those interested, here's the code (undoubtedly it can be much improved):
import sys
import time
import numpy
from matplotlib import pyplot
try:
from PyQt5 import QtCore, QtWidgets
print 'using PyQt5'
except:
from PyQt4 import QtCore, QtGui
print 'using PyQt4'
class StampTimer(QtCore.QTimer):
signal_quit = QtCore.pyqtSignal()
def __init__(self, number_of_samples, precise=False, *args, **kwargs):
super(StampTimer, self).__init__(*args, **kwargs)
if precise:
try:
self.setTimerType(QtCore.Qt.PreciseTimer)
print 'using precise timer (PyQt5)'
except:
print 'precise timer not available (PyQt4)'
self.number_of_samples = number_of_samples
self.stamps_ms = []
self.timeout.connect(self.stamp)
def start(self):
self.stamps_ms = []
super(StampTimer, self).start()
def stamp(self):
# Save a timestamp (use clock() instead of time())
self.stamps_ms.append(1e3 * time.clock())
# Quit when we reach the specified number of samples
if len(self.stamps_ms) == self.number_of_samples:
self.stop()
self.signal_quit.emit()
try:
app = QtWidgets.QApplication(sys.argv)
except:
app = QtGui.QApplication(sys.argv)
# Parameters
number_of_intervals = 100
intervals_requested_ms = range(1, 10)
intervals_requested_ms.extend(range(10, 60, 2))
intervals_requested_ms.extend(range(60, 200, 10))
intervals_requested_ms.append(1000)
# Variables
intervals_measured_ms = numpy.zeros(
(number_of_intervals, len(intervals_requested_ms)), float)
# Run
timer = StampTimer(number_of_samples=number_of_intervals+1, precise=False)
timer.signal_quit.connect(app.quit)
for index, interval_requested_ms in enumerate(intervals_requested_ms):
# For each interval in the list, we run the timer until we obtain
# the specified number of samples
timer.setInterval(interval_requested_ms)
timer.start()
app.exec_()
# Calculate statistics
intervals_measured_ms[:, index] = numpy.diff(timer.stamps_ms)
median_ms = numpy.median(intervals_measured_ms[:, index])
max_ms = numpy.max(intervals_measured_ms[:, index])
min_ms = numpy.min(intervals_measured_ms[:, index])
# Add to plot
print 'requested: {} ms, ' \
'measured: median(min/max) {:.1f} ({:.2f}/{:.2f}) ms'.format(
interval_requested_ms, median_ms, min_ms, max_ms)
pyplot.plot(interval_requested_ms, median_ms, 'ok')
pyplot.plot([interval_requested_ms]*2, [min_ms, max_ms], '-k')
# Plot result
pyplot.plot([0, 1000], [0, 1000], 'k--')
pyplot.xscale('log', nonposx='clip')
pyplot.yscale('log', nonposy='clip')
pyplot.xlabel('interval requested [ms]')
pyplot.ylabel(
'interval measured [ms] (median, n={})'.format(number_of_intervals))
pyplot.show()
Also check this answer for valuable insights.
Upvotes: 8
Reputation: 4414
You can achieve a better timer precision by setting timer type property to Qt::PreciseTimer
(the default type is Qt::CoarseTimer
).
From the docs:
Qt::PreciseTimer
– Precise timers try to keep millisecond accuracy.
Qt::CoarseTimer
– Coarse timers try to keep accuracy within 5% of the desired interval.
However, as pointed out by @Paul and @AlgirdasPreidžius, there is still no guarantee that the precision will be perfectly accurate.
Upvotes: 12
Reputation: 13238
Windows 7 is not RTOS, so there is no guarantee that the timer will fire exactly when you expect it to fire. This is not QTimer
issue, but the OS.
From QTimer
documentaion:
The accuracy of timers depends on the underlying operating system and hardware. Most platforms support a resolution of 1 millisecond, though the accuracy of the timer will not equal this resolution in many real-world situations. The accuracy also depends on the timer type. For Qt::PreciseTimer, QTimer will try to keep the accurance at 1 millisecond. Precise timers will also never time out earlier than expected.
Note, that the documentation states "will try to keep" and not "guarantees to keep".
Upvotes: 8