Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Running the <code>AcquireFrame</code> that blocks on mutexes in the GUI thread makes not much sense to me unless you wanted to trade off GUI reponsiveness for latency, but I doubt that you care about the latency here as the camera snaps single frames and you insist on processing them in the busy GUI thread in the first place.</p> <p>Secondly, there is nothing that Qt would do to prevent the callback from getting called from the other thread, other than the other thread having lower priority and being preempted by higher priority threads completely monopolizing the CPU.</p> <p>I would simply post an event to a QObject in the GUI thread (or any other QThread!) from the callback function. You can post events from any thread, it doesn't matter -- what matters is the receiver. <code>QCoreApplication::postEvent</code> is a static function, after all, and it doesn't check the current thread at all.</p> <p>In a complex application you probably want to have the logic in a dedicated controller QObject, and not spread across QWidget-derived classes. So you'd simply post the event to the controller instance.</p> <p>Do note that posting an event to an idle GUI thread will work exactly the same as using a mutex -- Qt's event loop uses a mutex and sleeps on that mutex and on messages from the OS. The beautiful thing is that Qt already does all the waiting for you, but the wait is interruptible. The posted event should have a high priority, so that it'll end up the first one in the queue and preempt all the other events. When you're ready to acquire the frame, but before you trigger it, you can probably call <code>QCoreApplication::flush()</code>. That's about it.</p> <p>There should be no problem in having a separate image processor QObject living in a dedicated QThread to leverage multicore machines. You can then process the image into a QImage, and forward that one to the GUI thread using another event, or simply via a signal-slot connection. You can probably also notify the GUI thread when you've acquired the frame but are only beginning to process it. That way it'd be more obvious to the user that something is happening. If image processing takes long, you can even send periodic updates that'd be mapped to a progress bar.</p> <p>The benchmark results (using release builds) are interesting but in line with the fact that Qt's event queues are internally protected by a mutex, and the event loop waits on that mutex. Oh, the results seem to be portable among mac and windows xp platforms.</p> <p>Using a naked wait condition is <em>not</em> the fastest way, but using a naked posted event is even <em>slower</em>. The fastest way is to use a queued signal-slot connection. In that case, the cost of posting an event to the same thread (that's what the <code>FrameProcessorEvents::tick()</code> does) seems to be negligible.</p> <h3>Mac</h3> <pre><code>warming caches... benchmarking... wait condition latency: avg=45us, max=152us, min=8us, n=1001 queued signal connection latency: avg=25us, max=82us, min=10us, n=1000 queued event latency: avg=71us, max=205us, min=14us, n=1000 </code></pre> <h3>Windows XP under VMWare Fusion</h3> <p>Note that results over 1ms are likely due to VMWare being not scheduled at the moment.</p> <pre><code>warming caches... benchmarking... wait condition latency: avg=93us, max=783us, min=8us, n=1000 queued signal connection latency: avg=46us, max=1799us, min=0us, n=1000 queued event latency: avg=117us, max=989us, min=18us, n=1001 </code></pre> <p>Below is the benchmarking code.</p> <pre><code>#include &lt;cstdio&gt; #include &lt;limits&gt; #include &lt;QtCore&gt; QTextStream out(stdout); class TimedBase : public QObject { public: TimedBase(QObject * parent = 0) : QObject(parent) { reset(); } friend QTextStream &amp; operator&lt;&lt;(QTextStream &amp; str, const TimedBase &amp; tb) { return str &lt;&lt; "avg=" &lt;&lt; tb.avg() &lt;&lt; "us, max=" &lt;&lt; tb.usMax &lt;&lt; "us, min=" &lt;&lt; tb.usMin &lt;&lt; "us, n=" &lt;&lt; tb.n; } void reset() { usMax = 0; n = 0; usMin = std::numeric_limits&lt;quint32&gt;::max(); usSum = 0; } protected: quint64 n, usMax, usMin, usSum; quint64 avg() const { return (n) ? usSum/n : 0; } void tock() { const quint64 t = elapsed.nsecsElapsed() / 1000; usSum += t; if (t &gt; usMax) usMax = t; if (t &lt; usMin) usMin = t; n ++; } QElapsedTimer elapsed; }; class FrameProcessorEvents : public TimedBase { Q_OBJECT public: FrameProcessorEvents(QObject * parent = 0) : TimedBase(parent) {} public slots: // can be invoked either from object thread or from the caller thread void tick() { elapsed.start(); QCoreApplication::postEvent(this, new QEvent(QEvent::User), 1000); } protected: void customEvent(QEvent * ev) { if (ev-&gt;type() == QEvent::User) tock(); } }; class FrameProcessorWait : public TimedBase { Q_OBJECT public: FrameProcessorWait(QObject * parent = 0) : TimedBase(parent) {} void start() { QTimer::singleShot(0, this, SLOT(spinner())); } public: // not a slot since it must be always invoked in the caller thread void tick() { elapsed.start(); wc.wakeAll(); } protected: QMutex mutex; QWaitCondition wc; protected slots: void spinner() { forever { QMutexLocker lock(&amp;mutex); if (wc.wait(&amp;mutex, 1000)) { tock(); } else { return; } } } }; FrameProcessorEvents * fpe; FrameProcessorWait * fpw; static const int avgCount = 1000; static const int period = 5; class FrameSender : public QObject { Q_OBJECT public: FrameSender(QObject * parent = 0) : QObject(parent), n(0), N(1) { QTimer::singleShot(0, this, SLOT(start())); } protected slots: void start() { out &lt;&lt; (N ? "warming caches..." : "benchmarking...") &lt;&lt; endl; // fire off a bunch of wait ticks n = avgCount; timer.disconnect(); connect(&amp;timer, SIGNAL(timeout()), SLOT(waitTick())); fpw-&gt;reset(); fpw-&gt;start(); timer.start(period); } void waitTick() { fpw-&gt;tick(); if (!n--) { if (!N) { out &lt;&lt; "wait condition latency: " &lt;&lt; *fpw &lt;&lt; endl; } // fire off a bunch of signal+event ticks n = avgCount; fpe-&gt;reset(); timer.disconnect(); connect(&amp;timer, SIGNAL(timeout()), fpe, SLOT(tick())); connect(&amp;timer, SIGNAL(timeout()), SLOT(signalTick())); } } void signalTick() { if (!n--) { if (!N) { out &lt;&lt; "queued signal connection latency: " &lt;&lt; *fpe &lt;&lt; endl; } // fire off a bunch of event-only ticks n = avgCount; fpe-&gt;reset(); timer.disconnect(); connect(&amp;timer, SIGNAL(timeout()), SLOT(eventTick())); } } void eventTick() { fpe-&gt;tick(); if (!n--) { if (!N) { out &lt;&lt; "queued event latency: " &lt;&lt; *fpe &lt;&lt; endl; } if (!N--) { qApp-&gt;exit(); } else { start(); } } } protected: QTimer timer; int n, N; }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QThread eThread; QThread wThread; eThread.start(QThread::TimeCriticalPriority); wThread.start(QThread::TimeCriticalPriority); fpw = new FrameProcessorWait(); fpe = new FrameProcessorEvents(); fpw-&gt;moveToThread(&amp;eThread); fpe-&gt;moveToThread(&amp;wThread); FrameSender s; a.exec(); eThread.exit(); wThread.exit(); eThread.wait(); wThread.wait(); return 0; } #include "main.moc" </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    1. COThank you for your suggestions, but in this case latency really is the issue: it's an embedded application where the GUI (such as it is) is non-interactive and exists only for displaying the current status of what's going on. At the point in the code where my example comes from, the main thread really has nothing to do except spin its wheels waiting for the frame to arrive, but needs to get going with it a.s.a.p. once the frame arrives. The overriding consideration is not, under any circumstances, to risk missing the frame when it comes. See my edit, above...
      singulars
    2. COSince internally Qt's even queues use mutexes just like you do, there's no real latency difference. Put the worker-upon-a-frame QObject to a separate realtime priority thread and that should do it. That QObject won't be receiving many other events and will stay ready for the event from the callback function. You can post an empty QEvent of a custom type, that's all. The nicety of this approach is that you can trivially stop the receiver thread, and there's no possibility of it getting stuck.
      singulars
    3. COAnd the benchmarks show that I'm right. There's no benefit to using a naked QMutex because that's what Qt does anyway. You'd think you decrease the cache pressure slightly by using a naked mutex since there's no Qt code executed between the wakeup and return of control to your code. Alas, the benchmarks show that this difference is immaterial and other factors take over.
      singulars
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload