Add thread Quality of Service API
With heterogenous CPU topology (and earlier, per-core clocks) OS developers have increasingly added "Quality of Service" APIs to thread scheduling, giving users a way to tell the OS what type of work the thread is expected to do. In the case of Apple they have a more extensive selection of service levels. Unlike what we currently see from Windows, the API there is actually much broader than just High, 'Default' and Eco, and covers more concrete uses like "Utility", "Background", "User-initiated", defined as -1 (Default), and a range from 0x09 (Background) to 0x21 (User-interactive)[0]. Currently there is no equivalent API for Linux though there is some push from various interested parties to add one (e.g. [1]). As mentioned, on Windows there is really only 3 levels, though it's defined as "do/don't throttle this thread" and "do as you wish". For Android I cannot really find anything equivalent beyond the thread priority. Discussed here: https://lists.qt-project.org/pipermail/development/2024-September/045694.html [0] https://developer.apple.com/documentation/foundation/qualityofservice [1] https://www.youtube.com/watch?v=RfgPWpTwTQo (Linux Plumbers Conference) Fixes: QTBUG-93946 Change-Id: Iabeaa7b61cec0bebd5c6a4bcf75a8e60dc0348dc Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
parent
073fae097c
commit
6013f82c94
@ -560,6 +560,46 @@ uint QThread::stackSize() const
|
||||
return d->stackSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.9
|
||||
|
||||
Set the Quality of Service level of the thread object to \a serviceLevel.
|
||||
This can only be called from the thread itself or before the thread is
|
||||
started!
|
||||
|
||||
This is currently only implemented on Apple platforms, and Windows.
|
||||
The function call will complete successfully on other platforms but will
|
||||
not currently have any effect.
|
||||
|
||||
\sa serviceLevel()
|
||||
*/
|
||||
void QThread::setServiceLevel(QualityOfService serviceLevel)
|
||||
{
|
||||
Q_D(QThread);
|
||||
QMutexLocker locker(&d->mutex);
|
||||
if (d->threadState != QThreadPrivate::Running) {
|
||||
d->serviceLevel = serviceLevel;
|
||||
} else {
|
||||
Q_ASSERT_X(isCurrentThread(), "QThread::setServiceLevel",
|
||||
"cannot change quality of service level of a separate, running, thread");
|
||||
d->setQualityOfServiceLevel(serviceLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.9
|
||||
|
||||
Return the current Quality of Service level of the thread.
|
||||
|
||||
\sa setServiceLevel()
|
||||
*/
|
||||
QThread::QualityOfService QThread::serviceLevel() const
|
||||
{
|
||||
Q_D(const QThread);
|
||||
QMutexLocker locker(&d->mutex);
|
||||
return d->serviceLevel;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
Transitions BindingStatusOrList to the binding status state. If we had a list of
|
||||
|
@ -51,6 +51,13 @@ public:
|
||||
InheritPriority
|
||||
};
|
||||
|
||||
enum class QualityOfService {
|
||||
Auto,
|
||||
High,
|
||||
Eco,
|
||||
};
|
||||
Q_ENUM(QualityOfService)
|
||||
|
||||
void setPriority(Priority priority);
|
||||
Priority priority() const;
|
||||
|
||||
@ -71,6 +78,9 @@ public:
|
||||
|
||||
bool isCurrentThread() const noexcept;
|
||||
|
||||
void setServiceLevel(QualityOfService serviceLevel);
|
||||
QualityOfService serviceLevel() const;
|
||||
|
||||
template <typename Function, typename... Args>
|
||||
[[nodiscard]] static QThread *create(Function &&f, Args &&... args);
|
||||
|
||||
|
@ -206,6 +206,12 @@ public:
|
||||
|
||||
bool wait(QMutexLocker<QMutex> &locker, QDeadlineTimer deadline);
|
||||
|
||||
QThread::QualityOfService serviceLevel = QThread::QualityOfService::Auto;
|
||||
void setQualityOfServiceLevel(QThread::QualityOfService qosLevel);
|
||||
#ifdef Q_OS_DARWIN
|
||||
qos_class_t nativeQualityOfServiceClass() const;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
QWaitCondition thread_done;
|
||||
|
||||
|
@ -62,6 +62,9 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
[[maybe_unused]]
|
||||
Q_STATIC_LOGGING_CATEGORY(lcQThread, "qt.core.thread", QtWarningMsg)
|
||||
|
||||
using namespace QtMiscUtils;
|
||||
|
||||
#if QT_CONFIG(thread)
|
||||
@ -315,6 +318,10 @@ void *QThreadPrivate::start(void *arg)
|
||||
if (thr->d_func()->priority & ThreadPriorityResetFlag) {
|
||||
thr->d_func()->setPriority(QThread::Priority(thr->d_func()->priority & ~ThreadPriorityResetFlag));
|
||||
}
|
||||
#ifndef Q_OS_DARWIN // For Darwin we set it as an attribute when starting the thread
|
||||
if (thr->d_func()->serviceLevel != QThread::QualityOfService::Auto)
|
||||
thr->d_func()->setQualityOfServiceLevel(thr->d_func()->serviceLevel);
|
||||
#endif
|
||||
|
||||
// threadId is set in QThread::start()
|
||||
Q_ASSERT(data->threadId.loadRelaxed() == QThread::currentThreadId());
|
||||
@ -680,6 +687,10 @@ void QThread::start(Priority priority)
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
#ifdef Q_OS_DARWIN
|
||||
if (d->serviceLevel != QThread::QualityOfService::Auto)
|
||||
pthread_attr_set_qos_class_np(&attr, d->nativeQualityOfServiceClass(), 0);
|
||||
#endif
|
||||
|
||||
d->priority = priority;
|
||||
|
||||
@ -901,6 +912,39 @@ void QThreadPrivate::setPriority(QThread::Priority threadPriority)
|
||||
#endif
|
||||
}
|
||||
|
||||
void QThreadPrivate::setQualityOfServiceLevel(QThread::QualityOfService qosLevel)
|
||||
{
|
||||
[[maybe_unused]]
|
||||
Q_Q(QThread);
|
||||
serviceLevel = qosLevel;
|
||||
#ifdef Q_OS_DARWIN
|
||||
qCDebug(lcQThread) << "Setting thread QoS class to" << serviceLevel << "for thread" << q;
|
||||
pthread_set_qos_class_self_np(nativeQualityOfServiceClass(), 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
qos_class_t QThreadPrivate::nativeQualityOfServiceClass() const
|
||||
{
|
||||
// @note Consult table[0] to see what the levels mean
|
||||
// [0] https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/PrioritizeWorkAtTheTaskLevel.html#//apple_ref/doc/uid/TP40013929-CH35-SW5
|
||||
// There are more levels but they have two other documented ones,
|
||||
// QOS_CLASS_BACKGROUND, which is below UTILITY, but has no guarantees
|
||||
// for scheduling (ie. the OS could choose to never give it CPU time),
|
||||
// and QOS_CLASS_USER_INITIATED, documented as being intended for
|
||||
// user-initiated actions, such as loading a text document.
|
||||
switch (serviceLevel) {
|
||||
case QThread::QualityOfService::Auto:
|
||||
return QOS_CLASS_DEFAULT;
|
||||
case QThread::QualityOfService::High:
|
||||
return QOS_CLASS_USER_INTERACTIVE;
|
||||
case QThread::QualityOfService::Eco:
|
||||
return QOS_CLASS_UTILITY;
|
||||
}
|
||||
Q_UNREACHABLE_RETURN(QOS_CLASS_DEFAULT);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // QT_CONFIG(thread)
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -28,8 +28,22 @@ SetThreadDescription(
|
||||
);
|
||||
}
|
||||
|
||||
#ifndef THREAD_POWER_THROTTLING_EXECUTION_SPEED
|
||||
#define THREAD_POWER_THROTTLING_EXECUTION_SPEED 0x1
|
||||
#define THREAD_POWER_THROTTLING_CURRENT_VERSION 1
|
||||
|
||||
typedef struct _THREAD_POWER_THROTTLING_STATE {
|
||||
ULONG Version;
|
||||
ULONG ControlMask;
|
||||
ULONG StateMask;
|
||||
} THREAD_POWER_THROTTLING_STATE;
|
||||
#endif
|
||||
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_STATIC_LOGGING_CATEGORY(lcQThread, "qt.core.thread", QtWarningMsg)
|
||||
|
||||
#if QT_CONFIG(thread)
|
||||
|
||||
void qt_watch_adopted_thread(const HANDLE adoptedThreadHandle, QThread *qthread);
|
||||
@ -251,6 +265,9 @@ unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(voi
|
||||
{
|
||||
QMutexLocker locker(&thr->d_func()->mutex);
|
||||
data->quitNow = thr->d_func()->exited;
|
||||
|
||||
if (thr->d_func()->serviceLevel != QThread::QualityOfService::Auto)
|
||||
thr->d_func()->setQualityOfServiceLevel(thr->d_func()->serviceLevel);
|
||||
}
|
||||
|
||||
data->ensureEventDispatcher();
|
||||
@ -270,6 +287,39 @@ unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(voi
|
||||
return 0;
|
||||
}
|
||||
|
||||
void QThreadPrivate::setQualityOfServiceLevel(QThread::QualityOfService qosLevel)
|
||||
{
|
||||
Q_Q(QThread);
|
||||
serviceLevel = qosLevel;
|
||||
|
||||
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS3)
|
||||
qCDebug(lcQThread) << "Setting thread QoS class to" << qosLevel << "for thread" << q;
|
||||
|
||||
THREAD_POWER_THROTTLING_STATE state;
|
||||
memset(&state, 0, sizeof(state));
|
||||
state.Version = THREAD_POWER_THROTTLING_CURRENT_VERSION;
|
||||
|
||||
switch (qosLevel) {
|
||||
case QThread::QualityOfService::Auto:
|
||||
state.ControlMask = 0; // Unset control of QoS
|
||||
state.StateMask = 0;
|
||||
break;
|
||||
case QThread::QualityOfService::Eco:
|
||||
state.ControlMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED;
|
||||
state.StateMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED;
|
||||
break;
|
||||
case QThread::QualityOfService::High:
|
||||
state.ControlMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED;
|
||||
state.StateMask = 0; // Ask to disable throttling
|
||||
break;
|
||||
}
|
||||
if (!SetThreadInformation(::GetCurrentThread(), THREAD_INFORMATION_CLASS::ThreadPowerThrottling,
|
||||
&state, sizeof(state))) {
|
||||
qErrnoWarning("Failed to set thread power throttling state");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
For regularly terminating threads, this will be called and executed by the thread as the
|
||||
last code before the thread exits. In that case, \a arg is the current QThread.
|
||||
|
@ -105,6 +105,8 @@ private slots:
|
||||
void terminateSelfStressTest();
|
||||
|
||||
void bindingListCleanupAfterDelete();
|
||||
|
||||
void qualityOfService();
|
||||
};
|
||||
|
||||
enum { one_minute = 60 * 1000, five_minutes = 5 * one_minute };
|
||||
@ -2104,5 +2106,33 @@ void tst_QThread::bindingListCleanupAfterDelete()
|
||||
QVERIFY(list->empty());
|
||||
}
|
||||
|
||||
void tst_QThread::qualityOfService()
|
||||
{
|
||||
QThread th;
|
||||
QThread::currentThread()->setObjectName("Main thread");
|
||||
th.setObjectName("test thread");
|
||||
auto guard = qScopeGuard([&th](){ th.quit(); th.wait(); });
|
||||
QCOMPARE(th.serviceLevel(), QThread::QualityOfService::Auto);
|
||||
th.setServiceLevel(QThread::QualityOfService::High);
|
||||
QCOMPARE(th.serviceLevel(), QThread::QualityOfService::High);
|
||||
th.setServiceLevel(QThread::QualityOfService::Eco);
|
||||
QCOMPARE(th.serviceLevel(), QThread::QualityOfService::Eco);
|
||||
|
||||
th.start();
|
||||
auto obj = std::make_unique<QObject>();
|
||||
obj->moveToThread(&th);
|
||||
|
||||
QThread::QualityOfService launchedThreadServiceLevel = {};
|
||||
QMetaObject::invokeMethod(obj.get(), [](){
|
||||
return QThread::currentThread()->serviceLevel();
|
||||
}, Qt::BlockingQueuedConnection, qReturnArg(launchedThreadServiceLevel));
|
||||
|
||||
QCOMPARE(launchedThreadServiceLevel, QThread::QualityOfService::Eco);
|
||||
|
||||
QMetaObject::invokeMethod(obj.get(), [](){
|
||||
QThread::currentThread()->setServiceLevel(QThread::QualityOfService::High);
|
||||
}, Qt::BlockingQueuedConnection);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QThread)
|
||||
#include "tst_qthread.moc"
|
||||
|
Loading…
x
Reference in New Issue
Block a user