QWinEventNotifier: unlink from event dispatcher

Instead of multiplexing all notifications into a single Qt event for
the event dispatcher, we can send 'WinEventAct' event directly for each
notifier which activated. This trick improves the performance (esp.
on a large number of events) and allows us to remove notifiers handling
from the event dispatcher completely.

As an alternative to sending Qt events, use of Windows' APC queue in
conjunction with waking up the Qt event loop from within the Windows
thread pool has been considered. However, that would lead to signal
emission asynchronous to the Qt event loop's operation, which is not
acceptable.

Thanks to Oswald Buddenhagen for the proposed idea.

[ChangeLog][QtCore][QAbstractEventDispatcher] The
{un}registerEventNotifier() member functions have been removed.
QWinEventNotifier is no longer needed to be registered in the
event dispatcher.

Change-Id: I140892fb909eaae0eabf2e07ebabcab78c43841c
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
This commit is contained in:
Alex Trotsenko 2020-09-18 19:10:35 +03:00
parent 0bfb39da85
commit aefd414ce2
11 changed files with 199 additions and 179 deletions

View File

@ -460,30 +460,6 @@ bool QAbstractEventDispatcher::filterNativeEvent(const QByteArray &eventType, vo
return false;
}
/*! \fn bool QAbstractEventDispatcher::registerEventNotifier(QWinEventNotifier *notifier)
This pure virtual method exists on windows only and has to be reimplemented by a Windows specific
event dispatcher implementation. \a notifier is the QWinEventNotifier instance to be registered.
The method should return true if the registration of \a notifier was successful, otherwise false.
QWinEventNotifier calls this method in it's constructor and there should never be a need to call this
method directly.
\sa QWinEventNotifier, unregisterEventNotifier()
*/
/*! \fn bool QAbstractEventDispatcher::unregisterEventNotifier(QWinEventNotifier *notifier)
This pure virtual method exists on windows only and has to be reimplemented by a Windows specific
event dispatcher implementation. \a notifier is the QWinEventNotifier instance to be unregistered.
QWinEventNotifier calls this method in it's destructor and there should never be a need to call this
method directly.
\sa QWinEventNotifier, registerEventNotifier()
*/
/*! \fn void QAbstractEventDispatcher::awake()
This signal is emitted after the event loop returns from a

View File

@ -49,10 +49,6 @@ class QAbstractNativeEventFilter;
class QAbstractEventDispatcherPrivate;
class QSocketNotifier;
#if defined(Q_OS_WIN) || defined(Q_CLANG_QDOC)
class QWinEventNotifier;
#endif
class Q_CORE_EXPORT QAbstractEventDispatcher : public QObject
{
Q_OBJECT
@ -88,11 +84,6 @@ public:
virtual int remainingTime(int timerId) = 0;
#if defined(Q_OS_WIN) || defined(Q_CLANG_QDOC)
virtual bool registerEventNotifier(QWinEventNotifier *notifier) = 0;
virtual void unregisterEventNotifier(QWinEventNotifier *notifier) = 0;
#endif
virtual void wakeUp() = 0;
virtual void interrupt() = 0;

View File

@ -47,12 +47,10 @@
#include "qset.h"
#include "qsocketnotifier.h"
#include "qvarlengtharray.h"
#include "qwineventnotifier.h"
#include "qelapsedtimer.h"
#include "qcoreapplication_p.h"
#include <private/qthread_p.h>
#include <private/qwineventnotifier_p.h>
QT_BEGIN_NAMESPACE
@ -99,7 +97,7 @@ LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPA
QEventDispatcherWin32Private::QEventDispatcherWin32Private()
: interrupt(false), internalHwnd(0),
getMessageHook(0), sendPostedEventsTimerId(0), wakeUps(0),
activateNotifiersPosted(false), activateEventNotifiersPosted(false)
activateNotifiersPosted(false)
{
}
@ -109,12 +107,6 @@ QEventDispatcherWin32Private::~QEventDispatcherWin32Private()
DestroyWindow(internalHwnd);
}
void QEventDispatcherWin32Private::activateEventNotifier(QWinEventNotifier * wen)
{
QEvent event(QEvent::WinEventAct);
QCoreApplication::sendEvent(wen, &event);
}
// This function is called by a workerthread
void WINAPI QT_WIN_CALLBACK qt_fast_timer_proc(uint timerId, uint /*reserved*/, DWORD_PTR user, DWORD_PTR /*reserved*/, DWORD_PTR /*reserved*/)
{
@ -455,14 +447,6 @@ void QEventDispatcherWin32Private::postActivateSocketNotifiers()
activateNotifiersPosted = PostMessage(internalHwnd, WM_QT_ACTIVATENOTIFIERS, 0, 0);
}
void QEventDispatcherWin32Private::postActivateEventNotifiers()
{
Q_Q(QEventDispatcherWin32);
if (!activateEventNotifiersPosted.fetchAndStoreRelease(true))
QCoreApplication::postEvent(q, new QEvent(QEvent::WinEventAct));
}
QEventDispatcherWin32::QEventDispatcherWin32(QObject *parent)
: QEventDispatcherWin32(*new QEventDispatcherWin32Private, parent)
{
@ -817,83 +801,6 @@ QEventDispatcherWin32::registeredTimers(QObject *object) const
return list;
}
bool QEventDispatcherWin32::registerEventNotifier(QWinEventNotifier *notifier)
{
Q_ASSERT(notifier);
#ifndef QT_NO_DEBUG
if (notifier->thread() != thread() || thread() != QThread::currentThread()) {
qWarning("QEventDispatcherWin32: event notifiers cannot be enabled from another thread");
return false;
}
#endif
Q_D(QEventDispatcherWin32);
if (d->winEventNotifierList.contains(notifier))
return true;
d->winEventNotifierList.append(notifier);
d->winEventNotifierListModified = true;
return QWinEventNotifierPrivate::get(notifier)->registerWaitObject();
}
void QEventDispatcherWin32::unregisterEventNotifier(QWinEventNotifier *notifier)
{
Q_ASSERT(notifier);
#ifndef QT_NO_DEBUG
if (notifier->thread() != thread() || thread() != QThread::currentThread()) {
qWarning("QEventDispatcherWin32: event notifiers cannot be disabled from another thread");
return;
}
#endif
doUnregisterEventNotifier(notifier);
}
void QEventDispatcherWin32::doUnregisterEventNotifier(QWinEventNotifier *notifier)
{
Q_D(QEventDispatcherWin32);
int i = d->winEventNotifierList.indexOf(notifier);
if (i == -1)
return;
d->winEventNotifierList.takeAt(i);
d->winEventNotifierListModified = true;
QWinEventNotifierPrivate *nd = QWinEventNotifierPrivate::get(notifier);
if (nd->waitHandle)
nd->unregisterWaitObject();
}
void QEventDispatcherWin32::activateEventNotifiers()
{
Q_D(QEventDispatcherWin32);
// Enable WM_QT_ACTIVATEWINEVENTS posting.
d->activateEventNotifiersPosted.fetchAndStoreAcquire(false);
// Activate signaled notifiers. Our winEventNotifierList can be modified in activation slots.
do {
d->winEventNotifierListModified = false;
for (int i = 0; i < d->winEventNotifierList.count(); ++i) {
QWinEventNotifier *notifier = d->winEventNotifierList.at(i);
QWinEventNotifierPrivate *nd = QWinEventNotifierPrivate::get(notifier);
if (nd->signaled.loadRelaxed()) {
nd->signaled.storeRelaxed(false);
nd->unregisterWaitObject();
d->activateEventNotifier(notifier);
}
}
} while (d->winEventNotifierListModified);
// Re-register the remaining activated notifiers.
for (int i = 0; i < d->winEventNotifierList.count(); ++i) {
QWinEventNotifier *notifier = d->winEventNotifierList.at(i);
QWinEventNotifierPrivate *nd = QWinEventNotifierPrivate::get(notifier);
if (!nd->waitHandle)
nd->registerWaitObject();
}
}
int QEventDispatcherWin32::remainingTime(int timerId)
{
#ifndef QT_NO_DEBUG
@ -958,10 +865,6 @@ void QEventDispatcherWin32::closingDown()
doUnregisterSocketNotifier((*(d->sn_except.begin()))->obj);
Q_ASSERT(d->active_fd.isEmpty());
// clean up any eventnotifiers
while (!d->winEventNotifierList.isEmpty())
doUnregisterEventNotifier(d->winEventNotifierList.first());
// clean up any timers
for (int i = 0; i < d->timerVec.count(); ++i)
d->unregisterTimer(d->timerVec.at(i));
@ -1009,9 +912,6 @@ bool QEventDispatcherWin32::event(QEvent *e)
case QEvent::Timer:
d->sendTimerEvent(static_cast<const QTimerEvent*>(e)->timerId());
break;
case QEvent::WinEventAct:
activateEventNotifiers();
break;
default:
break;
}

View File

@ -60,7 +60,6 @@
QT_BEGIN_NAMESPACE
class QWinEventNotifier;
class QEventDispatcherWin32Private;
// forward declaration
@ -86,10 +85,6 @@ public:
bool unregisterTimers(QObject *object) override;
QList<TimerInfo> registeredTimers(QObject *object) const override;
bool registerEventNotifier(QWinEventNotifier *notifier) override;
void unregisterEventNotifier(QWinEventNotifier *notifier) override;
void activateEventNotifiers();
int remainingTime(int timerId) override;
void wakeUp() override;
@ -106,7 +101,6 @@ protected:
QEventDispatcherWin32(QEventDispatcherWin32Private &dd, QObject *parent = nullptr);
virtual void sendPostedEvents();
void doUnregisterSocketNotifier(QSocketNotifier *notifier);
void doUnregisterEventNotifier(QWinEventNotifier *notifier);
private:
friend LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp);
@ -156,7 +150,6 @@ class Q_CORE_EXPORT QEventDispatcherWin32Private : public QAbstractEventDispatch
public:
QEventDispatcherWin32Private();
~QEventDispatcherWin32Private();
static QEventDispatcherWin32Private *get(QEventDispatcherWin32 *q) { return q->d_func(); }
QAtomicInt interrupt;
@ -186,12 +179,6 @@ public:
bool closingDown = false;
bool winEventNotifierListModified = false;
QAtomicInt activateEventNotifiersPosted;
QList<QWinEventNotifier *> winEventNotifierList;
void postActivateEventNotifiers();
void activateEventNotifier(QWinEventNotifier * wen);
QList<MSG> queuedUserInputEvents;
QList<MSG> queuedSocketEvents;
};

View File

@ -39,10 +39,8 @@
#include "qwineventnotifier_p.h"
#include "qeventdispatcher_win_p.h"
#include "qcoreapplication.h"
#include <private/qthread_p.h>
#include "qthread.h"
QT_BEGIN_NAMESPACE
@ -120,12 +118,8 @@ QWinEventNotifier::QWinEventNotifier(HANDLE hEvent, QObject *parent)
: QObject(*new QWinEventNotifierPrivate(hEvent, false), parent)
{
Q_D(QWinEventNotifier);
QAbstractEventDispatcher *eventDispatcher = d->threadData.loadRelaxed()->eventDispatcher.loadRelaxed();
if (Q_UNLIKELY(!eventDispatcher)) {
qWarning("QWinEventNotifier: Can only be used with threads started with QThread");
return;
}
eventDispatcher->registerEventNotifier(this);
d->registerWaitObject();
d->enabled = true;
}
@ -193,19 +187,20 @@ void QWinEventNotifier::setEnabled(bool enable)
return;
d->enabled = enable;
QAbstractEventDispatcher *eventDispatcher = d->threadData.loadRelaxed()->eventDispatcher.loadRelaxed();
if (!eventDispatcher) // perhaps application is shutting down
return;
if (Q_UNLIKELY(thread() != QThread::currentThread())) {
qWarning("QWinEventNotifier: Event notifiers cannot be enabled or disabled from another thread");
return;
}
if (enable) {
d->signaled.storeRelaxed(false);
eventDispatcher->registerEventNotifier(this);
} else {
eventDispatcher->unregisterEventNotifier(this);
// It is possible that the notifier was disabled after an event was already
// posted. In that case we set a state that indicates that such an obsolete
// event shall be ignored.
d->winEventActPosted.testAndSetRelaxed(QWinEventNotifierPrivate::Posted,
QWinEventNotifierPrivate::IgnorePosted);
d->registerWaitObject();
} else if (d->waitHandle != NULL) {
d->unregisterWaitObject();
}
}
@ -225,28 +220,33 @@ bool QWinEventNotifier::event(QEvent * e)
}
QObject::event(e); // will activate filters
if (e->type() == QEvent::WinEventAct) {
// Emit notification, but only if the event has not been invalidated
// since by the notifier being disabled, even if it was re-enabled
// again.
if (d->winEventActPosted.fetchAndStoreRelaxed(QWinEventNotifierPrivate::NotPosted)
== QWinEventNotifierPrivate::Posted && d->enabled) {
d->unregisterWaitObject();
emit activated(d->handleToEvent, QPrivateSignal());
if (d->enabled && d->waitHandle == NULL)
d->registerWaitObject();
}
return true;
}
return false;
}
static void CALLBACK wfsoCallback(void *context, BOOLEAN /*ignore*/)
void CALLBACK QWinEventNotifierPrivate::wfsoCallback(void *context, BOOLEAN /*ignore*/)
{
QWinEventNotifierPrivate *nd = reinterpret_cast<QWinEventNotifierPrivate *>(context);
QAbstractEventDispatcher *eventDispatcher = nd->threadData.loadRelaxed()->eventDispatcher.loadRelaxed();
// Happens when Q(Core)Application is destroyed before QWinEventNotifier.
// https://bugreports.qt.io/browse/QTBUG-70214
if (!eventDispatcher) { // perhaps application is shutting down
qWarning("QWinEventNotifier: no event dispatcher, application shutting down? Cannot deliver event.");
return;
// Do not post an event, if an event is already in the message queue. Note
// that an event that was previously invalidated will be reactivated.
if (nd->winEventActPosted.fetchAndStoreRelaxed(QWinEventNotifierPrivate::Posted)
== QWinEventNotifierPrivate::NotPosted) {
QCoreApplication::postEvent(nd->q_func(), new QEvent(QEvent::WinEventAct));
}
QEventDispatcherWin32Private *edp = QEventDispatcherWin32Private::get(
static_cast<QEventDispatcherWin32 *>(eventDispatcher));
nd->signaled.storeRelaxed(true);
edp->postActivateEventNotifiers();
}
bool QWinEventNotifierPrivate::registerWaitObject()

View File

@ -68,13 +68,15 @@ public:
QWinEventNotifierPrivate(HANDLE h, bool e)
: handleToEvent(h), enabled(e) {}
static QWinEventNotifierPrivate *get(QWinEventNotifier *q) { return q->d_func(); }
static void CALLBACK wfsoCallback(void *context, BOOLEAN /*ignore*/);
bool registerWaitObject();
void unregisterWaitObject();
HANDLE handleToEvent;
HANDLE waitHandle = NULL;
QAtomicInt signaled;
enum PostingState { NotPosted = 0, Posted, IgnorePosted };
QAtomicInt winEventActPosted;
bool enabled;
};

View File

@ -9,3 +9,6 @@ if(TARGET Qt::Widgets)
add_subdirectory(qmetaobject)
add_subdirectory(qobject)
endif()
if(win32_x_)
add_subdirectory(qwineventnotifier)
endif()

View File

@ -6,8 +6,12 @@ SUBDIRS = \
qobject \
qvariant \
qcoreapplication \
qtimer_vs_qmetaobject
qtimer_vs_qmetaobject \
qwineventnotifier
!qtHaveModule(widgets): SUBDIRS -= \
qmetaobject \
qobject
# This test is only applicable on Windows
!win32: SUBDIRS -= qwineventnotifier

View File

@ -0,0 +1,15 @@
# Generated from qwineventnotifier.pro.
#####################################################################
## tst_bench_qwineventnotifier Binary:
#####################################################################
qt_add_benchmark(tst_bench_qwineventnotifier
SOURCES
main.cpp
PUBLIC_LIBRARIES
Qt::Test
)
#### Keys ignored in scope 1:.:.:qwineventnotifier.pro:<TRUE>:
# TEMPLATE = "app"

View File

@ -0,0 +1,136 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QtCore/qglobal.h>
#include <QtCore/qwineventnotifier.h>
#include <QtCore/qeventloop.h>
#include <QtCore/qvector.h>
#include <QtCore/qelapsedtimer.h>
#include <QtCore/qt_windows.h>
class QWinEventNotifierBenchmark : public QObject
{
Q_OBJECT
private slots:
void waves_data();
void waves();
};
class EventsFactory : public QObject
{
Q_OBJECT
public:
explicit EventsFactory(int waves, int notifiers, int iterations)
: numberOfWaves(waves), numberOfNotifiers(notifiers),
numberOfIterations(iterations)
{
events.resize(notifiers);
for (int i = 0; i < notifiers; ++i) {
events[i] = CreateEvent(NULL, TRUE, FALSE, NULL);
QVERIFY(events[i] != NULL);
QWinEventNotifier *notifier = new QWinEventNotifier(events[i], this);
Q_CHECK_PTR(notifier);
connect(notifier, &QWinEventNotifier::activated, [i, this]() {
ResetEvent(this->events[i]);
if (--this->numberOfIterations == 0)
this->eventLoop.quit();
else
SetEvent(this->events[(i + 1) % this->numberOfNotifiers]);
});
connect(this, &EventsFactory::stop, [notifier]() {
notifier->setEnabled(false);
});
}
}
virtual ~EventsFactory()
{
for (auto event : events)
CloseHandle(event);
}
void run()
{
Q_ASSERT(numberOfWaves != 0);
int offset = 0;
for (int i = 0; i < numberOfWaves; ++i) {
SetEvent(events[offset]);
offset += qMax(1, numberOfNotifiers / numberOfWaves);
offset %= numberOfNotifiers;
}
eventLoop.exec();
}
signals:
void stop();
protected:
QVector<HANDLE> events;
QEventLoop eventLoop;
int numberOfWaves;
int numberOfNotifiers;
int numberOfIterations;
};
void QWinEventNotifierBenchmark::waves_data()
{
QTest::addColumn<int>("waves");
QTest::addColumn<int>("notifiers");
for (int waves : {1, 3, 10}) {
for (int notifiers : {10, 100, 1000})
QTest::addRow("waves: %d, notifiers: %d", waves, notifiers) << waves << notifiers;
}
}
void QWinEventNotifierBenchmark::waves()
{
QFETCH(int, waves);
QFETCH(int, notifiers);
const int iterations = 100000;
EventsFactory factory(waves, notifiers, iterations);
QElapsedTimer timer;
timer.start();
factory.run();
qDebug("Elapsed time: %.1f s", timer.elapsed() / 1000.0);
emit factory.stop();
}
QTEST_MAIN(QWinEventNotifierBenchmark)
#include "main.moc"

View File

@ -0,0 +1,6 @@
TEMPLATE = app
CONFIG += benchmark
QT = core testlib
TARGET = tst_bench_qwineventnotifier
SOURCES += main.cpp