Rework QtPrivate::Continuation to get rid of virtual calls and rename it

The Continuation class is a template and polymorphic, which likely
means that it instantiates a new type with a vtable for each
combination of template parameters.

Remove the Sync/Async continuation classes to get rid of the hierarcy.
Instead, encode all the information about the type of the continuation
into the class itself. This requires to add two new members:
* QThreadPool (which was previously a member of an async class)
* QRunnable (async class was previously derived from it).

We cannot really eliminate the need for QThreadPool and QRunnable
without a larger refactoring of the entire QFuture framework, because
QFutureInterface requires a QRunnable in its implementation.
However, this patch already allows us to get rid of all the virtual
functions in the Continuation implementation.

While on it, also remove the Function member and derive from
QtPrivate::CompactStorage<Function> instead.

This results in a significant reduce of a binary size for the user
projects, specially if they have a wide usage of continuations.
Taking tst_qfuture as an extreme example, the binary size on a
release build on macOS is reduced by ~15% - from 3723576 to 3156344
bytes.

The change seems to be BC, because all Continuation usages are from
the inline code in other template classes, so most probably a binary
linked with the older version of Qt will simply keep using the old
implementation.
Still, if the user project creates a shared library that utilizes
continuations, and builds the library with the default visibility
attributes, the private Continuation class gets exposed in the
exported methods. To avoid ODR violations in such cases, simply
rename the class to CompactContinuation.

Fixes: QTBUG-124909
Change-Id: I8c8263dbaf407d922aed2017d659bbc6fd87083d
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Ivan Solovev 2024-07-16 12:38:33 +02:00
parent 71a1f7c1ea
commit 699162c6fa
3 changed files with 71 additions and 85 deletions

View File

@ -280,7 +280,7 @@ private:
friend class QFutureInterfaceBase;
template<class Function, class ResultType, class ParentResultType>
friend class QtPrivate::Continuation;
friend class QtPrivate::CompactContinuation;
template<class Function, class ResultType>
friend class QtPrivate::CanceledHandler;
@ -339,7 +339,7 @@ QFuture<typename QFuture<T>::template ResultType<Function>>
QFuture<T>::then(QtFuture::Launch policy, Function &&function)
{
QFutureInterface<ResultType<Function>> promise(QFutureInterfaceBase::State::Pending);
QtPrivate::Continuation<std::decay_t<Function>, ResultType<Function>, T>::create(
QtPrivate::CompactContinuation<std::decay_t<Function>, ResultType<Function>, T>::create(
std::forward<Function>(function), this, promise, policy);
return promise.future();
}
@ -350,7 +350,7 @@ QFuture<typename QFuture<T>::template ResultType<Function>> QFuture<T>::then(QTh
Function &&function)
{
QFutureInterface<ResultType<Function>> promise(QFutureInterfaceBase::State::Pending);
QtPrivate::Continuation<std::decay_t<Function>, ResultType<Function>, T>::create(
QtPrivate::CompactContinuation<std::decay_t<Function>, ResultType<Function>, T>::create(
std::forward<Function>(function), this, promise, pool);
return promise.future();
}
@ -361,7 +361,7 @@ QFuture<typename QFuture<T>::template ResultType<Function>> QFuture<T>::then(QOb
Function &&function)
{
QFutureInterface<ResultType<Function>> promise(QFutureInterfaceBase::State::Pending);
QtPrivate::Continuation<std::decay_t<Function>, ResultType<Function>, T>::create(
QtPrivate::CompactContinuation<std::decay_t<Function>, ResultType<Function>, T>::create(
std::forward<Function>(function), this, promise, context);
return promise.future();
}

View File

@ -11,6 +11,7 @@
#endif
#include <QtCore/qglobal.h>
#include <QtCore/qfunctionaltools_impl.h>
#include <QtCore/qfutureinterface.h>
#include <QtCore/qthreadpool.h>
#include <QtCore/qexception.h>
@ -285,19 +286,37 @@ using IsForwardIterable =
std::forward_iterator_tag>;
template<typename Function, typename ResultType, typename ParentResultType>
class Continuation
class CompactContinuation : private CompactStorage<Function>
{
Q_DISABLE_COPY_MOVE(Continuation)
Q_DISABLE_COPY_MOVE(CompactContinuation)
public:
using Storage = CompactStorage<Function>;
template<typename F = Function>
Continuation(F &&func, const QFuture<ParentResultType> &f, QPromise<ResultType> &&p)
: promise(std::move(p)), parentFuture(f), function(std::forward<F>(func))
CompactContinuation(F &&func, const QFuture<ParentResultType> &f, QPromise<ResultType> &&p)
: Storage{std::forward<F>(func)}, promise(std::move(p)), parentFuture(f), type(Type::Sync)
{
}
virtual ~Continuation() = default;
template<typename F = Function>
CompactContinuation(F &&func, const QFuture<ParentResultType> &f, QPromise<ResultType> &&p,
QThreadPool *pool)
: Storage{std::forward<F>(func)}, promise(std::move(p)), parentFuture(f),
threadPool(pool), type(Type::Async)
{
runObj = QRunnable::create([this] {
this->runFunction();
delete this;
});
runObj->setAutoDelete(false);
}
~CompactContinuation() { delete runObj; }
bool execute();
QRunnable *runnable() const { return runObj; }
template<typename F = Function>
static void create(F &&func, QFuture<ParentResultType> *f, QFutureInterface<ResultType> &fi,
QtFuture::Launch policy);
@ -319,63 +338,30 @@ private:
void fulfillPromise(Args &&... args);
protected:
virtual void runImpl() = 0;
void runImpl()
{
if (type == Type::Sync) {
runFunction();
} else {
Q_ASSERT(runObj);
QThreadPool *pool = threadPool ? threadPool : QThreadPool::globalInstance();
pool->start(runObj);
}
}
void runFunction();
protected:
enum class Type : quint8 {
Sync,
Async
};
QPromise<ResultType> promise;
QFuture<ParentResultType> parentFuture;
Function function;
};
template<typename Function, typename ResultType, typename ParentResultType>
class SyncContinuation final : public Continuation<Function, ResultType, ParentResultType>
{
public:
template<typename F = Function>
SyncContinuation(F &&func, const QFuture<ParentResultType> &f, QPromise<ResultType> &&p)
: Continuation<Function, ResultType, ParentResultType>(std::forward<F>(func), f,
std::move(p))
{
}
~SyncContinuation() override = default;
private:
void runImpl() override { this->runFunction(); }
};
template<typename Function, typename ResultType, typename ParentResultType>
class AsyncContinuation final : public QRunnable,
public Continuation<Function, ResultType, ParentResultType>
{
public:
template<typename F = Function>
AsyncContinuation(F &&func, const QFuture<ParentResultType> &f, QPromise<ResultType> &&p,
QThreadPool *pool = nullptr)
: Continuation<Function, ResultType, ParentResultType>(std::forward<F>(func), f,
std::move(p)),
threadPool(pool)
{
}
~AsyncContinuation() override = default;
private:
void runImpl() override // from Continuation
{
QThreadPool *pool = threadPool ? threadPool : QThreadPool::globalInstance();
pool->start(this);
}
void run() override // from QRunnable
{
this->runFunction();
}
private:
QThreadPool *threadPool;
QThreadPool *threadPool = nullptr;
QRunnable *runObj = nullptr;
Type type;
};
#ifndef QT_NO_EXCEPTIONS
@ -415,7 +401,7 @@ private:
#endif
template<typename Function, typename ResultType, typename ParentResultType>
void Continuation<Function, ResultType, ParentResultType>::runFunction()
void CompactContinuation<Function, ResultType, ParentResultType>::runFunction()
{
promise.start();
@ -439,9 +425,9 @@ void Continuation<Function, ResultType, ParentResultType>::runFunction()
} else {
if constexpr (std::is_void_v<ParentResultType>) {
if constexpr (std::is_invocable_v<Function, QFuture<void>>)
function(parentFuture);
this->object()(parentFuture);
else
function();
this->object()();
} else if constexpr (std::is_invocable_v<Function, ParentResultType>) {
fulfillVoidPromise();
} else {
@ -449,7 +435,7 @@ void Continuation<Function, ResultType, ParentResultType>::runFunction()
// that nothing unexpected happened.
static_assert(std::is_invocable_v<Function, QFuture<ParentResultType>>,
"The continuation is not invocable with the provided arguments");
function(parentFuture);
this->object()(parentFuture);
}
}
#ifndef QT_NO_EXCEPTIONS
@ -461,7 +447,7 @@ void Continuation<Function, ResultType, ParentResultType>::runFunction()
}
template<typename Function, typename ResultType, typename ParentResultType>
bool Continuation<Function, ResultType, ParentResultType>::execute()
bool CompactContinuation<Function, ResultType, ParentResultType>::execute()
{
Q_ASSERT(parentFuture.isFinished());
@ -513,7 +499,7 @@ private:
template<typename Function, typename ResultType, typename ParentResultType>
template<typename F>
void Continuation<Function, ResultType, ParentResultType>::create(F &&func,
void CompactContinuation<Function, ResultType, ParentResultType>::create(F &&func,
QFuture<ParentResultType> *f,
QFutureInterface<ResultType> &fi,
QtFuture::Launch policy)
@ -538,20 +524,20 @@ void Continuation<Function, ResultType, ParentResultType>::create(F &&func,
auto continuation = [func = std::forward<F>(func), fi, promise_ = QPromise(fi), pool,
launchAsync](const QFutureInterfaceBase &parentData) mutable {
const auto parent = QFutureInterface<ParentResultType>(parentData).future();
Continuation<Function, ResultType, ParentResultType> *continuationJob = nullptr;
CompactContinuation<Function, ResultType, ParentResultType> *continuationJob = nullptr;
if (launchAsync) {
auto asyncJob = new AsyncContinuation<Function, ResultType, ParentResultType>(
auto asyncJob = new CompactContinuation<Function, ResultType, ParentResultType>(
std::forward<Function>(func), parent, std::move(promise_), pool);
fi.setRunnable(asyncJob);
fi.setRunnable(asyncJob->runnable());
continuationJob = asyncJob;
} else {
continuationJob = new SyncContinuation<Function, ResultType, ParentResultType>(
continuationJob = new CompactContinuation<Function, ResultType, ParentResultType>(
std::forward<Function>(func), parent, std::move(promise_));
}
bool isLaunched = continuationJob->execute();
// If continuation is successfully launched, AsyncContinuation will be deleted
// by the QThreadPool which has started it. Synchronous continuation will be
// from the QRunnable's lambda. Synchronous continuation will be
// executed immediately, so it's safe to always delete it here.
if (!(launchAsync && isLaunched)) {
delete continuationJob;
@ -563,7 +549,7 @@ void Continuation<Function, ResultType, ParentResultType>::create(F &&func,
template<typename Function, typename ResultType, typename ParentResultType>
template<typename F>
void Continuation<Function, ResultType, ParentResultType>::create(F &&func,
void CompactContinuation<Function, ResultType, ParentResultType>::create(F &&func,
QFuture<ParentResultType> *f,
QFutureInterface<ResultType> &fi,
QThreadPool *pool)
@ -576,11 +562,11 @@ void Continuation<Function, ResultType, ParentResultType>::create(F &&func,
auto continuation = [func = std::forward<F>(func), promise_ = QPromise(fi),
pool](const QFutureInterfaceBase &parentData) mutable {
const auto parent = QFutureInterface<ParentResultType>(parentData).future();
auto continuationJob = new AsyncContinuation<Function, ResultType, ParentResultType>(
auto continuationJob = new CompactContinuation<Function, ResultType, ParentResultType>(
std::forward<Function>(func), parent, std::move(promise_), pool);
bool isLaunched = continuationJob->execute();
// If continuation is successfully launched, AsyncContinuation will be deleted
// by the QThreadPool which has started it.
// from the QRunnable's lambda.
if (!isLaunched) {
delete continuationJob;
continuationJob = nullptr;
@ -600,7 +586,7 @@ void watchContinuation(const QObject *context, Continuation &&c, QFutureInterfac
template<typename Function, typename ResultType, typename ParentResultType>
template<typename F>
void Continuation<Function, ResultType, ParentResultType>::create(F &&func,
void CompactContinuation<Function, ResultType, ParentResultType>::create(F &&func,
QFuture<ParentResultType> *f,
QFutureInterface<ResultType> &fi,
QObject *context)
@ -613,7 +599,7 @@ void Continuation<Function, ResultType, ParentResultType>::create(F &&func,
// destroyed and, if it is not yet finished, cancelled.
auto continuation = [func = std::forward<F>(func), parent = *f,
promise_ = QPromise(fi)]() mutable {
SyncContinuation<Function, ResultType, ParentResultType> continuationJob(
CompactContinuation<Function, ResultType, ParentResultType> continuationJob(
std::forward<Function>(func), parent, std::move(promise_));
continuationJob.execute();
};
@ -622,7 +608,7 @@ void Continuation<Function, ResultType, ParentResultType>::create(F &&func,
}
template<typename Function, typename ResultType, typename ParentResultType>
void Continuation<Function, ResultType, ParentResultType>::fulfillPromiseWithResult()
void CompactContinuation<Function, ResultType, ParentResultType>::fulfillPromiseWithResult()
{
if constexpr (std::is_copy_constructible_v<ParentResultType>)
fulfillPromise(parentFuture.result());
@ -631,16 +617,16 @@ void Continuation<Function, ResultType, ParentResultType>::fulfillPromiseWithRes
}
template<typename Function, typename ResultType, typename ParentResultType>
void Continuation<Function, ResultType, ParentResultType>::fulfillVoidPromise()
void CompactContinuation<Function, ResultType, ParentResultType>::fulfillVoidPromise()
{
if constexpr (std::is_copy_constructible_v<ParentResultType>)
function(parentFuture.result());
this->object()(parentFuture.result());
else
function(parentFuture.takeResult());
this->object()(parentFuture.takeResult());
}
template<typename Function, typename ResultType, typename ParentResultType>
void Continuation<Function, ResultType, ParentResultType>::fulfillPromiseWithVoidResult()
void CompactContinuation<Function, ResultType, ParentResultType>::fulfillPromiseWithVoidResult()
{
if constexpr (std::is_invocable_v<Function, QFuture<void>>)
fulfillPromise(parentFuture);
@ -650,9 +636,9 @@ void Continuation<Function, ResultType, ParentResultType>::fulfillPromiseWithVoi
template<typename Function, typename ResultType, typename ParentResultType>
template<class... Args>
void Continuation<Function, ResultType, ParentResultType>::fulfillPromise(Args &&... args)
void CompactContinuation<Function, ResultType, ParentResultType>::fulfillPromise(Args &&... args)
{
promise.addResult(std::invoke(function, std::forward<Args>(args)...));
promise.addResult(std::invoke(this->object(), std::forward<Args>(args)...));
}
template<class T>

View File

@ -28,7 +28,7 @@ class QFutureWatcherBasePrivate;
namespace QtPrivate {
template<typename Function, typename ResultType, typename ParentResultType>
class Continuation;
class CompactContinuation;
class ExceptionStore;
@ -171,7 +171,7 @@ private:
friend class QFutureWatcherBasePrivate;
template<typename Function, typename ResultType, typename ParentResultType>
friend class QtPrivate::Continuation;
friend class QtPrivate::CompactContinuation;
template<class Function, class ResultType>
friend class QtPrivate::CanceledHandler;