From 9bbef41a5022ab887c501a0ab70c1f19279ffa94 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sat, 3 Jun 2017 10:04:28 +0200 Subject: [PATCH] C++11 optimizations and (basic) benchmark Make continuation methods const (then/fail/finally) and ensure that the resolved promise value/error is copied only when required, same for user lambdas (dispatching result is now fully handled by the PromiseData). --- src/qtpromise/qpromise.h | 78 ++++----- src/qtpromise/qpromise.inl | 230 ++++++++++--------------- src/qtpromise/qpromise_p.h | 219 ++++++++++++++++++----- src/qtpromise/qpromiseerror.h | 34 +++- src/qtpromise/qpromisefuture.h | 3 +- src/qtpromise/qpromisehelpers.h | 6 +- tests/auto/auto.pro | 1 + tests/auto/benchmark/benchmark.pro | 5 + tests/auto/benchmark/tst_benchmark.cpp | 223 ++++++++++++++++++++++++ 9 files changed, 558 insertions(+), 241 deletions(-) create mode 100644 tests/auto/benchmark/benchmark.pro create mode 100644 tests/auto/benchmark/tst_benchmark.cpp diff --git a/src/qtpromise/qpromise.h b/src/qtpromise/qpromise.h index 80c65fa..e7aa208 100644 --- a/src/qtpromise/qpromise.h +++ b/src/qtpromise/qpromise.h @@ -16,33 +16,9 @@ class QPromiseBase public: using Type = T; - virtual ~QPromiseBase() { } - - bool isFulfilled() const { return m_d->resolved && !m_d->rejected; } - bool isRejected() const { return m_d->resolved && m_d->rejected; } - bool isPending() const { return !m_d->resolved; } - - template - inline typename QtPromisePrivate::PromiseHandler::Promise - then(TFulfilled fulfilled, TRejected rejected = nullptr); - - template - inline typename QtPromisePrivate::PromiseHandler::Promise - fail(TRejected rejected); - - inline QPromise wait() const; - -public: // STATIC - template - inline static QPromise reject(const E& error); - -protected: - friend class QPromiseResolve; - friend class QPromiseReject; - - QExplicitlySharedDataPointer > m_d; - - inline QPromiseBase(); + QPromiseBase(const QPromiseBase& other): m_d(other.m_d) {} + QPromiseBase(const QPromise& other): m_d(other.m_d) {} + QPromiseBase(QPromiseBase&& other) { swap(other); } template ::count == 1, int>::type = 0> inline QPromiseBase(F resolver); @@ -50,9 +26,33 @@ protected: template ::count != 1, int>::type = 0> inline QPromiseBase(F resolver); - virtual void notify(const typename QtPromisePrivate::PromiseData::HandlerList& handlers) const = 0; + virtual ~QPromiseBase() { } - inline void dispatch(); + bool isFulfilled() const { return m_d->isFulfilled(); } + bool isRejected() const { return m_d->isRejected(); } + bool isPending() const { return m_d->isPending(); } + + template + inline typename QtPromisePrivate::PromiseHandler::Promise + then(const TFulfilled& fulfilled, const TRejected& rejected = nullptr) const; + + template + inline typename QtPromisePrivate::PromiseHandler::Promise + fail(TRejected&& rejected) const; + + inline QPromise wait() const; + + void swap(QPromiseBase& other) { qSwap(m_d, other.m_d); } + +public: // STATIC + template + inline static QPromise reject(E&& error); + +protected: + friend class QPromiseResolve; + friend class QPromiseReject; + + QExplicitlySharedDataPointer > m_d; }; template @@ -60,22 +60,17 @@ class QPromise: public QPromiseBase { public: template - QPromise(F resolver): QPromiseBase(resolver) { } + QPromise(F&& resolver): QPromiseBase(std::forward(resolver)) { } template - inline QPromise finally(THandler handler); + inline QPromise finally(THandler handler) const; public: // STATIC inline static QPromise > all(const QVector >& promises); - inline static QPromise resolve(const T& value); - -protected: - inline void notify(const typename QtPromisePrivate::PromiseData::HandlerList& handlers) const Q_DECL_OVERRIDE; + inline static QPromise resolve(T&& value); private: friend class QPromiseBase; - - QPromise(const QPromiseBase& p) : QPromiseBase(p) { } }; template <> @@ -83,22 +78,17 @@ class QPromise: public QPromiseBase { public: template - QPromise(F resolver): QPromiseBase(resolver) { } + QPromise(F&& resolver): QPromiseBase(std::forward(resolver)) { } template - inline QPromise finally(THandler handler); + inline QPromise finally(THandler handler) const; public: // STATIC inline static QPromise all(const QVector >& promises); inline static QPromise resolve(); -protected: - inline void notify(const typename QtPromisePrivate::PromiseData::HandlerList& handlers) const Q_DECL_OVERRIDE; - private: friend class QPromiseBase; - - QPromise(const QPromiseBase& p) : QPromiseBase(p) { } }; } // namespace QtPromise diff --git a/src/qtpromise/qpromise.inl b/src/qtpromise/qpromise.inl index c598c13..6a0ef99 100644 --- a/src/qtpromise/qpromise.inl +++ b/src/qtpromise/qpromise.inl @@ -4,141 +4,87 @@ namespace QtPromise { -template +template class QPromiseResolve { public: - QPromiseResolve(const QPromise& p) - : m_promise(p) + QPromiseResolve(QPromise p) + : m_promise(new QPromise(std::move(p))) { } - void operator()(const T& value) const + void operator()(const T& value) const { + resolve(value); + } + + void operator()(T&& value) const { - auto p = m_promise; - if (p.isPending()) { - p.m_d->rejected = false; - p.m_d->resolved = true; - p.m_d->value = value; - p.dispatch(); - } + resolve(std::move(value)); } private: - QPromise m_promise; + QSharedPointer > m_promise; + + template + void resolve(U&& value) const + { + Q_ASSERT(!m_promise.isNull()); + if (m_promise->isPending()) { + m_promise->m_d->resolve(std::forward(value)); + m_promise->m_d->dispatch(); + } + } }; template <> class QPromiseResolve { public: - QPromiseResolve(const QPromise& p) - : m_promise(p) + QPromiseResolve(QPromise p) + : m_promise(new QPromise(std::move(p))) { } void operator()() const { - auto p = m_promise; - if (p.isPending()) { - p.m_d->rejected = false; - p.m_d->resolved = true; - p.dispatch(); + Q_ASSERT(!m_promise.isNull()); + if (m_promise->isPending()) { + m_promise->m_d->resolve(); + m_promise->m_d->dispatch(); } } private: - QPromise m_promise; + QSharedPointer > m_promise; }; -template +template class QPromiseReject { public: - QPromiseReject() + QPromiseReject(QPromise p) + : m_promise(new QPromise(std::move(p))) { } - QPromiseReject(const QPromise& p) - : m_promise(p) - { } - - void operator()(const QPromiseError& error) const + template + void operator()(E&& error) const { - auto p = m_promise; - if (p.isPending()) { - p.m_d->rejected = true; - p.m_d->resolved = true; - p.m_d->error = error; - p.dispatch(); + Q_ASSERT(!m_promise.isNull()); + if (m_promise->isPending()) { + m_promise->m_d->reject(std::forward(error)); + m_promise->m_d->dispatch(); } } private: - QPromise m_promise; + QSharedPointer > m_promise; }; -template -template -inline typename QtPromisePrivate::PromiseHandler::Promise -QPromiseBase::then(TFulfilled fulfilled, TRejected rejected) -{ - using namespace QtPromisePrivate; - using PromiseType = typename PromiseHandler::Promise; - - PromiseType next([=]( - const QPromiseResolve& resolve, - const QPromiseReject& reject) { - m_d->handlers << PromiseHandler::create(fulfilled, resolve, reject); - m_d->catchers << PromiseCatcher::create(rejected, resolve, reject); - }); - - if (m_d->resolved) { - dispatch(); - } - - return next; -} - -template -template -inline typename QtPromisePrivate::PromiseHandler::Promise -QPromiseBase::fail(TRejected rejected) -{ - return then(nullptr, rejected); -} - -template -inline QPromise QPromiseBase::wait() const -{ - // @TODO wait timeout + global timeout - while (!m_d->resolved) { - QCoreApplication::processEvents(QEventLoop::AllEvents); - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - } - - return *this; -} - -template -template -inline QPromise QPromiseBase::reject(const E& error) -{ - return QPromise([=](const QPromiseResolve&) { - throw error; - }); -} - -template -inline QPromiseBase::QPromiseBase() - : m_d(new QtPromisePrivate::PromiseData()) -{ -} - template template ::count == 1, int>::type> inline QPromiseBase::QPromiseBase(F resolver) : m_d(new QtPromisePrivate::PromiseData()) { - auto resolve = QPromiseResolve(*this); - auto reject = QPromiseReject(*this); + QPromiseResolve resolve(*this); + QPromiseReject reject(*this); try { resolver(resolve); @@ -152,8 +98,8 @@ template ::count inline QPromiseBase::QPromiseBase(F resolver) : m_d(new QtPromisePrivate::PromiseData()) { - auto resolve = QPromiseResolve(*this); - auto reject = QPromiseReject(*this); + QPromiseResolve resolve(*this); + QPromiseReject reject(*this); try { resolver(resolve, reject); @@ -163,33 +109,59 @@ inline QPromiseBase::QPromiseBase(F resolver) } template -inline void QPromiseBase::dispatch() +template +inline typename QtPromisePrivate::PromiseHandler::Promise +QPromiseBase::then(const TFulfilled& fulfilled, const TRejected& rejected) const { using namespace QtPromisePrivate; + using PromiseType = typename PromiseHandler::Promise; - Q_ASSERT(m_d->resolved); + PromiseType next([&]( + const QPromiseResolve& resolve, + const QPromiseReject& reject) { + m_d->addHandler(PromiseHandler::create(fulfilled, resolve, reject)); + m_d->addCatcher(PromiseCatcher::create(rejected, resolve, reject)); + }); - typename PromiseData::HandlerList handlers; - typename PromiseData::CatcherList catchers; - - handlers.swap(m_d->handlers); - catchers.swap(m_d->catchers); - - if (m_d->rejected) { - const QPromiseError error = m_d->error; - qtpromise_defer([=]() { - for (auto catcher: catchers) { - catcher(error); - } - }); - } else { - notify(handlers); + if (!m_d->isPending()) { + m_d->dispatch(); } + + return next; +} + +template +template +inline typename QtPromisePrivate::PromiseHandler::Promise +QPromiseBase::fail(TRejected&& rejected) const +{ + return then(nullptr, std::forward(rejected)); +} + +template +inline QPromise QPromiseBase::wait() const +{ + // @TODO wait timeout + global timeout + while (m_d->isPending()) { + QCoreApplication::processEvents(QEventLoop::AllEvents); + QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); + } + + return *this; +} + +template +template +inline QPromise QPromiseBase::reject(E&& error) +{ + return QPromise([&](const QPromiseResolve&, const QPromiseReject& reject) { + reject(std::forward(error)); + }); } template template -inline QPromise QPromise::finally(THandler handler) +inline QPromise QPromise::finally(THandler handler) const { return this->then([=](const T& res) { return QPromise::resolve().then(handler).then([=](){ @@ -220,7 +192,7 @@ inline QPromise > QPromise::all(const QVector >& promi QSharedPointer > results(new QVector(count)); for (int i=0; i(promises[i]).then([=](const T& res) mutable { + promises[i].then([=](const T& res) mutable { (*results)[i] = res; if (--(*remaining) == 0) { resolve(*results); @@ -236,26 +208,15 @@ inline QPromise > QPromise::all(const QVector >& promi } template -inline QPromise QPromise::resolve(const T& value) +inline QPromise QPromise::resolve(T&& value) { - return QPromise([=](const QPromiseResolve& resolve) { - resolve(value); - }); -} - -template -inline void QPromise::notify(const typename QtPromisePrivate::PromiseData::HandlerList& handlers) const -{ - const T value = this->m_d->value; - QtPromisePrivate::qtpromise_defer([handlers, value]() { - for (auto handler: handlers) { - handler(value); - } + return QPromise([&](const QPromiseResolve& resolve) { + resolve(std::forward(value)); }); } template -inline QPromise QPromise::finally(THandler handler) +inline QPromise QPromise::finally(THandler handler) const { return this->then([=]() { return QPromise::resolve().then(handler).then([](){}); @@ -281,7 +242,7 @@ inline QPromise QPromise::all(const QVector >& promis QSharedPointer remaining(new int(promises.size())); for (const auto& promise: promises) { - QPromise(promise).then([=]() { + promise.then([=]() { if (--(*remaining) == 0) { resolve(); } @@ -302,13 +263,4 @@ inline QPromise QPromise::resolve() }); } -inline void QPromise::notify(const typename QtPromisePrivate::PromiseData::HandlerList& handlers) const -{ - QtPromisePrivate::qtpromise_defer([handlers]() { - for (const auto& handler: handlers) { - handler(); - } - }); -} - } // namespace QtPromise diff --git a/src/qtpromise/qpromise_p.h b/src/qtpromise/qpromise_p.h index 28c71f4..8626854 100644 --- a/src/qtpromise/qpromise_p.h +++ b/src/qtpromise/qpromise_p.h @@ -7,6 +7,7 @@ // Qt #include +#include #include #include @@ -26,9 +27,9 @@ class QPromiseReject; namespace QtPromisePrivate { template -inline void qtpromise_defer(F f) +inline void qtpromise_defer(F&& f) { - QTimer::singleShot(0, f); + QTimer::singleShot(0, std::forward(f)); } template @@ -46,11 +47,11 @@ template struct PromiseFulfill { static void call( - const T& value, + T&& value, const QtPromise::QPromiseResolve& resolve, const QtPromise::QPromiseReject&) { - resolve(value); + resolve(std::move(value)); } }; @@ -58,7 +59,7 @@ template struct PromiseFulfill > { static void call( - QtPromise::QPromise promise, + const QtPromise::QPromise& promise, const QtPromise::QPromiseResolve& resolve, const QtPromise::QPromiseReject& reject) { @@ -76,7 +77,10 @@ template <> struct PromiseFulfill > { template - static void call(TPromise promise, TResolve resolve, TReject reject) + static void call( + const TPromise& promise, + const TResolve& resolve, + const TReject& reject) { promise.then( [=]() { @@ -95,11 +99,10 @@ struct PromiseDispatch using ResType = Unqualified; template - static void call(const T& value, THandler handler, TResolve resolve, TReject reject) + static void call(const T& value, THandler handler, const TResolve& resolve, const TReject& reject) { try { - const ResType res = handler(value); - PromiseFulfill::call(res, resolve, reject); + PromiseFulfill::call(handler(value), resolve, reject); } catch (...) { reject(std::current_exception()); } @@ -112,7 +115,7 @@ struct PromiseDispatch using Promise = QtPromise::QPromise; template - static void call(const T& value, THandler handler, TResolve resolve, TReject reject) + static void call(const T& value, THandler handler, const TResolve& resolve, const TReject& reject) { try { handler(value); @@ -130,11 +133,10 @@ struct PromiseDispatch using ResType = Unqualified; template - static void call(THandler handler, TResolve resolve, TReject reject) + static void call(THandler handler, const TResolve& resolve, const TReject& reject) { try { - const ResType res = handler(); - PromiseFulfill::call(res, resolve, reject); + PromiseFulfill::call(handler(), resolve, reject); } catch (...) { reject(std::current_exception()); } @@ -147,7 +149,7 @@ struct PromiseDispatch using Promise = QtPromise::QPromise; template - static void call(THandler handler, TResolve resolve, TReject reject) + static void call(THandler handler, const TResolve& resolve, const TReject& reject) { try { handler(); @@ -165,10 +167,13 @@ struct PromiseHandler using Promise = typename PromiseDispatch::Promise; template - static std::function create(THandler handler, TResolve resolve, TReject reject) + static std::function create( + const THandler& handler, + const TResolve& resolve, + const TReject& reject) { return [=](const T& value) { - PromiseDispatch::call(value, handler, resolve, reject); + PromiseDispatch::call(value, std::move(handler), resolve, reject); }; } }; @@ -180,7 +185,10 @@ struct PromiseHandler using Promise = typename PromiseDispatch::Promise; template - static std::function create(THandler handler, TResolve resolve, TReject reject) + static std::function create( + const THandler& handler, + const TResolve& resolve, + const TReject& reject) { return [=](const T&) { PromiseDispatch::call(handler, resolve, reject); @@ -195,7 +203,10 @@ struct PromiseHandler using Promise = typename PromiseDispatch::Promise; template - static std::function create(THandler handler, TResolve resolve, TReject reject) + static std::function create( + const THandler& handler, + const TResolve& resolve, + const TReject& reject) { return [=]() { PromiseDispatch::call(handler, resolve, reject); @@ -209,12 +220,15 @@ struct PromiseHandler using Promise = QtPromise::QPromise; template - static std::function create(std::nullptr_t, TResolve resolve, TReject reject) + static std::function create( + std::nullptr_t, + const TResolve& resolve, + const TReject& reject) { return [=](const T& value) { // 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled, // promise2 must be fulfilled with the same value as promise1. - PromiseFulfill::call(value, resolve, reject); + PromiseFulfill::call(std::move(T(value)), resolve, reject); }; } }; @@ -225,7 +239,10 @@ struct PromiseHandler using Promise = QtPromise::QPromise; template - static std::function create(std::nullptr_t, TResolve resolve, TReject) + static std::function create( + std::nullptr_t, + const TResolve& resolve, + const TReject&) { return [=]() { // 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled, @@ -238,11 +255,13 @@ struct PromiseHandler template ::first> struct PromiseCatcher { - using Functor = std::function; using ResType = typename std::result_of::type; template - static Functor create(THandler handler, TResolve resolve, TReject reject) + static std::function create( + const THandler& handler, + const TResolve& resolve, + const TReject& reject) { return [=](const QtPromise::QPromiseError& error) { try { @@ -259,11 +278,13 @@ struct PromiseCatcher template struct PromiseCatcher { - using Functor = std::function; using ResType = typename std::result_of::type; template - static Functor create(THandler handler, TResolve resolve, TReject reject) + static std::function create( + const THandler& handler, + const TResolve& resolve, + const TReject& reject) { return [=](const QtPromise::QPromiseError& error) { try { @@ -278,10 +299,11 @@ struct PromiseCatcher template struct PromiseCatcher { - using Functor = std::function; - template - static Functor create(std::nullptr_t, TResolve, TReject reject) + static std::function create( + std::nullptr_t, + const TResolve&, + const TReject& reject) { return [=](const QtPromise::QPromiseError& error) { // 2.2.7.4. If onRejected is not a function and promise1 is rejected, @@ -291,33 +313,142 @@ struct PromiseCatcher } }; -template -struct PromiseDataBase: public QSharedData -{ - using ErrorType = QtPromise::QPromiseError; - using CatcherList = QVector >; +template class PromiseData; - bool resolved; - bool rejected; - ErrorType error; - CatcherList catchers; +template +class PromiseDataBase: public QSharedData +{ +public: + using Error = QtPromise::QPromiseError; + using Catcher = std::function; + + virtual ~PromiseDataBase() {} + + bool isFulfilled() const { return m_settled && m_error.isNull(); } + bool isRejected() const { return m_settled && !m_error.isNull(); } + bool isPending() const { return !m_settled; } + + void addCatcher(Catcher catcher) + { + m_catchers.append(std::move(catcher)); + } + + void reject(Error error) + { + Q_ASSERT(m_error.isNull()); + m_error.reset(new Error(std::move(error))); + setSettled(); + } + + void dispatch() + { + Q_ASSERT(!isPending()); + + if (isFulfilled()) { + notify(); + return; + } + + Q_ASSERT(isRejected()); + QSharedPointer error = m_error; + QVector catchers(std::move(m_catchers)); + for (const auto& catcher: catchers) { + qtpromise_defer([=]() { + catcher(*error); + }); + } + } + +protected: + void setSettled() + { + Q_ASSERT(!m_settled); + m_settled = true; + } + + virtual void notify() = 0; + +private: + bool m_settled = false; + QVector m_catchers; + QSharedPointer m_error; }; template -struct PromiseData: PromiseDataBase +class PromiseData: public PromiseDataBase { - using HandlerList = QVector >; +public: + using Handler = std::function; - HandlerList handlers; - T value; + void addHandler(Handler handler) + { + m_handlers.append(std::move(handler)); + } + + const T& value() const + { + Q_ASSERT(!m_value.isNull()); + return *m_value; + } + + void resolve(T&& value) + { + Q_ASSERT(m_value.isNull()); + m_value.reset(new T(std::move(value))); + this->setSettled(); + } + + void resolve(const T& value) + { + Q_ASSERT(m_value.isNull()); + m_value.reset(new T(value)); + this->setSettled(); + } + + void notify() Q_DECL_OVERRIDE + { + Q_ASSERT(this->isFulfilled()); + QSharedPointer value(m_value); + QVector handlers(std::move(m_handlers)); + for (const auto& handler: handlers) { + qtpromise_defer([=]() { + handler(*value); + }); + } + } + +private: + QVector m_handlers; + QSharedPointer m_value; }; template <> -struct PromiseData: PromiseDataBase +class PromiseData: public PromiseDataBase { - using HandlerList = QVector >; +public: + using Handler = std::function; - HandlerList handlers; + void addHandler(Handler handler) + { + m_handlers.append(std::move(handler)); + } + + void resolve() { setSettled(); } + +protected: + void notify() Q_DECL_OVERRIDE + { + Q_ASSERT(isFulfilled()); + QVector handlers(std::move(m_handlers)); + for (const auto& handler: handlers) { + qtpromise_defer([=]() { + handler(); + }); + } + } + +private: + QVector m_handlers; }; } // namespace QtPromise diff --git a/src/qtpromise/qpromiseerror.h b/src/qtpromise/qpromiseerror.h index e21a148..2c96fdb 100644 --- a/src/qtpromise/qpromiseerror.h +++ b/src/qtpromise/qpromiseerror.h @@ -9,32 +9,48 @@ namespace QtPromise { class QPromiseError { public: - QPromiseError() - : exception(nullptr) - { } - template QPromiseError(const T& value) - : exception(nullptr) { try { throw value; } catch(...) { - exception = std::current_exception(); + m_exception = std::current_exception(); } } QPromiseError(const std::exception_ptr& exception) - : exception(exception) + : m_exception(exception) { } + QPromiseError(const QPromiseError& error) + : m_exception(error.m_exception) + { } + + QPromiseError(QPromiseError&& other) + : m_exception(nullptr) + { + swap(other); + } + + QPromiseError& operator =(QPromiseError other) + { + swap(other); + return *this; + } + + void swap(QPromiseError& other) + { + qSwap(m_exception, other.m_exception); + } + void rethrow() const { - std::rethrow_exception(exception); + std::rethrow_exception(m_exception); } private: - std::exception_ptr exception; + std::exception_ptr m_exception; }; } // namespace QtPromise diff --git a/src/qtpromise/qpromisefuture.h b/src/qtpromise/qpromisefuture.h index ff97599..d08f06c 100644 --- a/src/qtpromise/qpromisefuture.h +++ b/src/qtpromise/qpromisefuture.h @@ -48,8 +48,7 @@ struct PromiseFulfill > watcher->waitForFinished(); reject(QtPromise::QPromiseCanceledException()); } else { - T res = watcher->result(); - PromiseFulfill::call(res, resolve, reject); + PromiseFulfill::call(watcher->result(), resolve, reject); } } catch(...) { reject(std::current_exception()); diff --git a/src/qtpromise/qpromisehelpers.h b/src/qtpromise/qpromisehelpers.h index f901733..972729e 100644 --- a/src/qtpromise/qpromisehelpers.h +++ b/src/qtpromise/qpromisehelpers.h @@ -7,14 +7,14 @@ namespace QtPromise { template -typename QtPromisePrivate::PromiseDeduce::Type qPromise(const T& value) +typename QtPromisePrivate::PromiseDeduce::Type qPromise(T&& value) { using namespace QtPromisePrivate; using Promise = typename PromiseDeduce::Type; - return Promise([=]( + return Promise([&]( const QPromiseResolve& resolve, const QPromiseReject& reject) { - PromiseFulfill::call(value, resolve, reject); + PromiseFulfill::call(std::forward(value), resolve, reject); }); } diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 3b0b440..da47758 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -1,5 +1,6 @@ TEMPLATE = subdirs SUBDIRS += \ + benchmark \ future \ helpers \ qpromise \ diff --git a/tests/auto/benchmark/benchmark.pro b/tests/auto/benchmark/benchmark.pro new file mode 100644 index 0000000..cc5bd31 --- /dev/null +++ b/tests/auto/benchmark/benchmark.pro @@ -0,0 +1,5 @@ +QT += concurrent +TARGET = tst_benchmark +SOURCES += $$PWD/tst_benchmark.cpp + +include(../tests.pri) diff --git a/tests/auto/benchmark/tst_benchmark.cpp b/tests/auto/benchmark/tst_benchmark.cpp new file mode 100644 index 0000000..7d2587b --- /dev/null +++ b/tests/auto/benchmark/tst_benchmark.cpp @@ -0,0 +1,223 @@ +// QtPromise +#include + +// Qt +#include +#include + +using namespace QtPromise; + +class tst_benchmark: public QObject +{ + Q_OBJECT + + void valueResolve(); + void valueReject(); + void valueThen(); + void errorReject(); +private Q_SLOTS: + void errorThen(); + +}; // class tst_benchmark + +QTEST_MAIN(tst_benchmark) +#include "tst_benchmark.moc" + +struct Logs { + int ctor = 0; + int copy = 0; + int move = 0; + int refs = 0; + + void reset() { + ctor = 0; + copy = 0; + move = 0; + refs = 0; + } +}; + +struct Logger +{ + Logger() { logs().ctor++; logs().refs++; } + Logger(const Logger&) { logs().copy++; logs().refs++; } + Logger(Logger&&) { logs().move++; logs().refs++; } + ~Logger() { logs().refs--; } + + Logger& operator=(const Logger&) { logs().copy++; return *this; } + Logger& operator=(Logger&&) { logs().move++; return *this; } + +public: // STATICS + static Logs& logs() { static Logs logs; return logs; } +}; + +struct Data: public Logger +{ + Data(int v): Logger(), m_value(v) {} + int value() const { return m_value; } + +private: + int m_value; +}; + +void tst_benchmark::valueResolve() +{ + { // should move the value when resolved by rvalue + Data::logs().reset(); + QPromise([&](const QPromiseResolve& resolve) { + resolve(Data(42)); + }).wait(); + + QCOMPARE(Data::logs().ctor, 1); + QCOMPARE(Data::logs().copy, 0); + QCOMPARE(Data::logs().move, 1); // move value to the promise data + QCOMPARE(Data::logs().refs, 0); + } + { // should create one copy of the value when resolved by lvalue + Data::logs().reset(); + QPromise([&](const QPromiseResolve& resolve) { + Data value(42); + resolve(value); + }).wait(); + + QCOMPARE(Data::logs().ctor, 1); + QCOMPARE(Data::logs().copy, 1); // copy value to the promise data + QCOMPARE(Data::logs().move, 0); + QCOMPARE(Data::logs().refs, 0); + } +} + +void tst_benchmark::valueReject() +{ + { // should not create any data if rejected + Data::logs().reset(); + QPromise([&](const QPromiseResolve&, const QPromiseReject& reject) { + reject(QString("foo")); + }).wait(); + + QCOMPARE(Data::logs().ctor, 0); + QCOMPARE(Data::logs().copy, 0); + QCOMPARE(Data::logs().move, 0); + QCOMPARE(Data::logs().refs, 0); + } +} + +void tst_benchmark::valueThen() +{ + { // should not copy value on continutation if fulfilled + int value = -1; + Data::logs().reset(); + QPromise::resolve(Data(42)).then([&](const Data& res) { + value = res.value(); + }).wait(); + + QCOMPARE(Data::logs().ctor, 1); + QCOMPARE(Data::logs().copy, 0); + QCOMPARE(Data::logs().move, 1); // move value to the promise data + QCOMPARE(Data::logs().refs, 0); + QCOMPARE(value, 42); + } + { // should not create value on continutation if rejected + int value = -1; + QString error; + Data::logs().reset(); + QPromise::reject(QString("foo")).then([&](const Data& res) { + value = res.value(); + }, [&](const QString& err) { + error = err; + }).wait(); + + QCOMPARE(Data::logs().ctor, 0); + QCOMPARE(Data::logs().copy, 0); + QCOMPARE(Data::logs().move, 0); // move value to the promise data + QCOMPARE(Data::logs().refs, 0); + QCOMPARE(error, QString("foo")); + QCOMPARE(value, -1); + } + { // should move the returned value when fulfilled + int value = -1; + Data::logs().reset(); + QPromise::resolve(42).then([&](int res) { + return Data(res+2); + }).then([&](const Data& res) { + value = res.value(); + }).wait(); + + QCOMPARE(Data::logs().ctor, 1); + QCOMPARE(Data::logs().copy, 0); + QCOMPARE(Data::logs().move, 1); // move values to the next promise data + QCOMPARE(Data::logs().refs, 0); + QCOMPARE(value, 44); + } + { // should not create any data if handler throws + Data::logs().reset(); + QPromise::resolve(42).then([&](int res) { + throw QString("foo"); + return Data(res+2); + }).wait(); + + QCOMPARE(Data::logs().ctor, 0); + QCOMPARE(Data::logs().copy, 0); + QCOMPARE(Data::logs().move, 0); + QCOMPARE(Data::logs().refs, 0); + } +} + +void tst_benchmark::errorReject() +{ + { // should create one copy of the error when rejected by rvalue + Data::logs().reset(); + QPromise([&](const QPromiseResolve&, const QPromiseReject& reject) { + reject(Data(42)); + }).wait(); + + QCOMPARE(Data::logs().ctor, 1); + QCOMPARE(Data::logs().copy, 1); // copy value in std::exception_ptr + QCOMPARE(Data::logs().move, 0); + QCOMPARE(Data::logs().refs, 0); + } + { // should create one copy of the error when rejected by lvalue (no extra copy) + Data::logs().reset(); + QPromise([&](const QPromiseResolve&, const QPromiseReject& reject) { + Data error(42); + reject(error); + }).wait(); + + QCOMPARE(Data::logs().ctor, 1); + QCOMPARE(Data::logs().copy, 1); // copy value to the promise data + QCOMPARE(Data::logs().move, 0); + QCOMPARE(Data::logs().refs, 0); + } +} + +void tst_benchmark::errorThen() +{ + { // should not copy error on continutation if rejected + int value = -1; + Data::logs().reset(); + QPromise::reject(Data(42)).fail([&](const Data& res) { + value = res.value(); + }).wait(); + + QCOMPARE(Data::logs().ctor, 1); + QCOMPARE(Data::logs().copy, 1); // (initial) copy value in std::exception_ptr + QCOMPARE(Data::logs().move, 0); + QCOMPARE(Data::logs().refs, 0); + QCOMPARE(value, 42); + } + { // should not copy error on continutation if rethrown + int value = -1; + Data::logs().reset(); + QPromise::reject(Data(42)).fail([](const Data&) { + throw; + }).fail([&](const Data& res) { + value = res.value(); + }).wait(); + + QCOMPARE(Data::logs().ctor, 1); + QCOMPARE(Data::logs().copy, 1); // (initial) copy value in std::exception_ptr + QCOMPARE(Data::logs().move, 0); + QCOMPARE(Data::logs().refs, 0); + QCOMPARE(value, 42); + } +}