Simplify the safeguarding logic in notify()

The logic in notify() was doing quite a bit more work than
it needed to. By inserting a dummy node after the current one instead of
replacing it, we can avoid half of the data shuffling that has been
happening and also don't need a back pointer when executing the
notification.

Also avoid calling a semi expensive destructor of QPropertyObserver.

Reduces the overhead of notify() by ~30%.

Pick-to: dev 6.0.0
Change-Id: I7ce16bcf9cd9c4368c18bf875fc959223452fd4f
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Lars Knoll 2020-11-25 13:13:37 +01:00
parent c63901c5f3
commit e8ef871e35
3 changed files with 80 additions and 118 deletions

View File

@ -398,10 +398,6 @@ void QPropertyObserver::setSource(const QPropertyBindingData &property)
QPropertyObserver::~QPropertyObserver() QPropertyObserver::~QPropertyObserver()
{ {
if (next.tag() == ActivelyExecuting) {
if (nodeState)
*nodeState = nullptr;
}
QPropertyObserverPointer d{this}; QPropertyObserverPointer d{this};
d.unlink(); d.unlink();
} }
@ -415,8 +411,6 @@ QPropertyObserver::QPropertyObserver(QPropertyObserver &&other) noexcept
next->prev = &next; next->prev = &next;
if (prev) if (prev)
prev.setPointer(this); prev.setPointer(this);
if (next.tag() == ActivelyExecuting)
*nodeState = this;
} }
QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) noexcept QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) noexcept
@ -435,8 +429,6 @@ QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) noexc
next->prev = &next; next->prev = &next;
if (prev) if (prev)
prev.setPointer(this); prev.setPointer(this);
if (next.tag() == ActivelyExecuting)
*nodeState = this;
return *this; return *this;
} }
@ -455,21 +447,21 @@ void QPropertyObserverPointer::unlink()
void QPropertyObserverPointer::setChangeHandler(QPropertyObserver::ChangeHandler changeHandler) void QPropertyObserverPointer::setChangeHandler(QPropertyObserver::ChangeHandler changeHandler)
{ {
Q_ASSERT(ptr->next.tag() != QPropertyObserver::ActivelyExecuting); Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder);
ptr->changeHandler = changeHandler; ptr->changeHandler = changeHandler;
ptr->next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler); ptr->next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler);
} }
void QPropertyObserverPointer::setAliasedProperty(QUntypedPropertyData *property) void QPropertyObserverPointer::setAliasedProperty(QUntypedPropertyData *property)
{ {
Q_ASSERT(ptr->next.tag() != QPropertyObserver::ActivelyExecuting); Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder);
ptr->aliasedPropertyData = property; ptr->aliasedPropertyData = property;
ptr->next.setTag(QPropertyObserver::ObserverNotifiesAlias); ptr->next.setTag(QPropertyObserver::ObserverNotifiesAlias);
} }
void QPropertyObserverPointer::setBindingToMarkDirty(QPropertyBindingPrivate *binding) void QPropertyObserverPointer::setBindingToMarkDirty(QPropertyBindingPrivate *binding)
{ {
Q_ASSERT(ptr->next.tag() != QPropertyObserver::ActivelyExecuting); Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder);
ptr->bindingToMarkDirty = binding; ptr->bindingToMarkDirty = binding;
ptr->next.setTag(QPropertyObserver::ObserverNotifiesBinding); ptr->next.setTag(QPropertyObserver::ObserverNotifiesBinding);
} }
@ -479,55 +471,26 @@ void QPropertyObserverPointer::setBindingToMarkDirty(QPropertyBindingPrivate *bi
QPropertyObserverNodeProtector is a RAII wrapper which takes care of the internal switching logic QPropertyObserverNodeProtector is a RAII wrapper which takes care of the internal switching logic
for QPropertyObserverPointer::notify (described ibidem) for QPropertyObserverPointer::notify (described ibidem)
*/ */
template <QPropertyObserver::ObserverTag tag>
struct [[nodiscard]] QPropertyObserverNodeProtector { struct [[nodiscard]] QPropertyObserverNodeProtector {
QPropertyObserver m_placeHolder; QPropertyObserverBase m_placeHolder;
QPropertyObserver *&m_observer; QPropertyObserverNodeProtector(QPropertyObserver *observer)
union {
QPropertyBindingPrivate *m_binding;
QPropertyObserver::ChangeHandler m_changeHandler;
};
QPropertyObserverNodeProtector(QPropertyObserver *&observer)
: m_observer(observer)
{ {
static_assert(tag == QPropertyObserver::ObserverNotifiesBinding || // insert m_placeholder after observer into the linked list
tag == QPropertyObserver::ObserverNotifiesChangeHandler); QPropertyObserver *next = observer->next.data();
if constexpr (tag == QPropertyObserver::ObserverNotifiesBinding) m_placeHolder.next = next;
m_binding = m_observer->bindingToMarkDirty; observer->next = static_cast<QPropertyObserver *>(&m_placeHolder);
else if (next)
m_changeHandler = m_observer->changeHandler; next->prev = &m_placeHolder.next;
switchNodes(m_placeHolder, m_observer); m_placeHolder.prev = &observer->next;
m_observer->nodeState = &m_observer; m_placeHolder.next.setTag(QPropertyObserver::ObserverIsPlaceholder);
m_observer->next.setTag(QPropertyObserver::ActivelyExecuting);
m_placeHolder.next.setTag(QPropertyObserver::ActivelyExecuting);
} }
QPropertyObserver *next() const { return m_placeHolder.next.data(); }
~QPropertyObserverNodeProtector() { ~QPropertyObserverNodeProtector() {
if (m_observer) { QPropertyObserverPointer d{static_cast<QPropertyObserver *>(&m_placeHolder)};
if constexpr (tag == QPropertyObserver::ObserverNotifiesBinding) d.unlink();
m_observer->bindingToMarkDirty = m_binding;
else
m_observer->changeHandler = m_changeHandler;
switchNodes(*m_observer, &m_placeHolder);
m_observer->next.setTag(tag);
} }
// set tag to a safer value where we don't execute anything in the dtor
m_placeHolder.next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler);
}
/*!
\internal
replaces a node \a observer in the list with another node \a placeholder which must not be in the list
*/
static void switchNodes(QPropertyObserver &placeHolder, QPropertyObserver *observer) {
placeHolder.next = std::exchange(observer->next, {});
placeHolder.prev = std::exchange(observer->prev, {});
if (placeHolder.next) {
placeHolder.next->prev = &placeHolder.next;
}
if (placeHolder.prev)
placeHolder.prev.setPointer(&placeHolder);
};
}; };
/*! \internal /*! \internal
@ -551,27 +514,33 @@ void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding
* However, complication arise due to the fact that the triggered operations might modify the list, * However, complication arise due to the fact that the triggered operations might modify the list,
* which includes deletion and move of the current and next nodes. * which includes deletion and move of the current and next nodes.
* Therefore, we take a few safety precautions: * Therefore, we take a few safety precautions:
* 1. Before executing any action which might modify the list, we replace the actual node in the list with * 1. Before executing any action which might modify the list, we insert a placeholder node after the current node.
* a placeholder node. As that one is stack allocated and owned by us, we can rest assured that it is * As that one is stack allocated and owned by us, we can rest assured that it is
* still there after the action has executed, and placeHolder->next points to the actual next node in the list. * still there after the action has executed, and placeHolder->next points to the actual next node in the list.
* Note that taking next at the beginning of the loop does not work, as the execuated action might either move * Note that taking next at the beginning of the loop does not work, as the execuated action might either move
* or delete that node. * or delete that node.
* 2. To properly handle deletion or moves of the real current node, we store a pointer to a pointer to itself in * 2. After the triggered action has finished, we can use the next pointer in the placeholder node as a safe way to
* its nodeState. Whenever the node is reallocated and moved, we update that pointer to point to its new * retrieve the next node.
* location. If the node is actually deleted, we set it to nullptr. * 3. Some care needs to be taken to avoid infinite recursion with change handlers, so we add an extra test there, that
* 3. After the triggered action has finished, we can use that information to restore the list to contain the actual * checks whether we're already have the same change handler in our call stack. This can be done by checking whether
* node again. We either switch the nodes with the real nodes current location, or, if the real node has been * the node after the current one is a placeholder node.
* deleted, we simply unlink the temporary node.
*/ */
while (observer) { while (observer) {
QPropertyObserver *next = nullptr; QPropertyObserver *next = observer->next.data();
char preventBug[1] = {'\0'}; // QTBUG-87245 char preventBug[1] = {'\0'}; // QTBUG-87245
Q_UNUSED(preventBug); Q_UNUSED(preventBug);
switch (observer->next.tag()) { switch (QPropertyObserver::ObserverTag(observer->next.tag())) {
case QPropertyObserver::ObserverNotifiesChangeHandler: case QPropertyObserver::ObserverNotifiesChangeHandler:
if (auto handlerToCall = observer->changeHandler) { {
auto handlerToCall = observer->changeHandler;
// prevent recursion
if (next && next->next.tag() == QPropertyObserver::ObserverIsPlaceholder) {
observer = next->next.data();
continue;
}
// both evaluateIfDirtyAndReturnTrueIfValueChanged and handlerToCall might modify the list // both evaluateIfDirtyAndReturnTrueIfValueChanged and handlerToCall might modify the list
QPropertyObserverNodeProtector<QPropertyObserver::ObserverNotifiesChangeHandler> protector(observer); QPropertyObserverNodeProtector protector(observer);
if (!knownIfPropertyChanged && triggeringBinding) { if (!knownIfPropertyChanged && triggeringBinding) {
knownIfPropertyChanged = true; knownIfPropertyChanged = true;
propertyChanged = triggeringBinding->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr); propertyChanged = triggeringBinding->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr);
@ -579,28 +548,22 @@ void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding
if (!propertyChanged) if (!propertyChanged)
return; return;
handlerToCall(observer, propertyDataPtr); handlerToCall(observer, propertyDataPtr);
next = protector.m_placeHolder.next.data(); next = protector.next();
} else {
next = observer->next.data();
}
break; break;
}
case QPropertyObserver::ObserverNotifiesBinding: case QPropertyObserver::ObserverNotifiesBinding:
if (auto bindingToMarkDirty = observer->bindingToMarkDirty) { {
QPropertyObserverNodeProtector<QPropertyObserver::ObserverNotifiesBinding> protector(observer); auto bindingToMarkDirty = observer->bindingToMarkDirty;
QPropertyObserverNodeProtector protector(observer);
bindingToMarkDirty->markDirtyAndNotifyObservers(); bindingToMarkDirty->markDirtyAndNotifyObservers();
next = protector.m_placeHolder.next.data(); next = protector.next();
} else { break;
next = observer->next.data();
} }
break;
case QPropertyObserver::ObserverNotifiesAlias: case QPropertyObserver::ObserverNotifiesAlias:
next = observer->next.data();
break; break;
case QPropertyObserver::ActivelyExecuting: case QPropertyObserver::ObserverIsPlaceholder:
// recursion is already properly handled somewhere else // recursion is already properly handled somewhere else
return; break;
default:
Q_UNREACHABLE();
} }
observer = next; observer = next;
} }

View File

@ -205,8 +205,9 @@ namespace Qt {
struct QPropertyObserverPrivate; struct QPropertyObserverPrivate;
struct QPropertyObserverPointer; struct QPropertyObserverPointer;
class QPropertyObserver;
class Q_CORE_EXPORT QPropertyObserver class QPropertyObserverBase
{ {
public: public:
// Internal // Internal
@ -214,10 +215,33 @@ public:
ObserverNotifiesBinding, // observer was installed to notify bindings that obsverved property changed ObserverNotifiesBinding, // observer was installed to notify bindings that obsverved property changed
ObserverNotifiesChangeHandler, // observer is a change handler, which runs on every change ObserverNotifiesChangeHandler, // observer is a change handler, which runs on every change
ObserverNotifiesAlias, // used for QPropertyAlias ObserverNotifiesAlias, // used for QPropertyAlias
ActivelyExecuting // the observer is currently evaluated in QPropertyObserver::notifyObservers or its ObserverIsPlaceholder // the observer before this one is currently evaluated in QPropertyObserver::notifyObservers.
// placeholder. We only can store 4 different values, therefore those two conflate };
protected:
using ChangeHandler = void (*)(QPropertyObserver*, QUntypedPropertyData *);
private:
friend struct QPropertyObserverNodeProtector;
friend class QPropertyObserver;
friend struct QPropertyObserverPointer;
friend struct QPropertyBindingDataPointer;
friend class QPropertyBindingPrivate;
QTaggedPointer<QPropertyObserver, ObserverTag> next;
// prev is a pointer to the "next" element within the previous node, or to the "firstObserverPtr" if it is the
// first node.
QtPrivate::QTagPreservingPointerToPointer<QPropertyObserver, ObserverTag> prev;
union {
QPropertyBindingPrivate *bindingToMarkDirty = nullptr;
ChangeHandler changeHandler;
QUntypedPropertyData *aliasedPropertyData;
};
}; };
class Q_CORE_EXPORT QPropertyObserver : public QPropertyObserverBase
{
public:
constexpr QPropertyObserver() = default; constexpr QPropertyObserver() = default;
QPropertyObserver(QPropertyObserver &&other) noexcept; QPropertyObserver(QPropertyObserver &&other) noexcept;
QPropertyObserver &operator=(QPropertyObserver &&other) noexcept; QPropertyObserver &operator=(QPropertyObserver &&other) noexcept;
@ -229,7 +253,6 @@ public:
void setSource(const QtPrivate::QPropertyBindingData &property); void setSource(const QtPrivate::QPropertyBindingData &property);
protected: protected:
using ChangeHandler = void (*)(QPropertyObserver*, QUntypedPropertyData *);
QPropertyObserver(ChangeHandler changeHandler); QPropertyObserver(ChangeHandler changeHandler);
QPropertyObserver(QUntypedPropertyData *aliasedPropertyPtr); QPropertyObserver(QUntypedPropertyData *aliasedPropertyPtr);
@ -240,26 +263,9 @@ protected:
private: private:
QTaggedPointer<QPropertyObserver, ObserverTag> next;
// prev is a pointer to the "next" element within the previous node, or to the "firstObserverPtr" if it is the
// first node.
QtPrivate::QTagPreservingPointerToPointer<QPropertyObserver, ObserverTag> prev;
union {
QPropertyBindingPrivate *bindingToMarkDirty = nullptr;
ChangeHandler changeHandler;
QUntypedPropertyData *aliasedPropertyData;
QPropertyObserver **nodeState;
};
QPropertyObserver(const QPropertyObserver &) = delete; QPropertyObserver(const QPropertyObserver &) = delete;
QPropertyObserver &operator=(const QPropertyObserver &) = delete; QPropertyObserver &operator=(const QPropertyObserver &) = delete;
friend struct QPropertyObserverPointer;
friend struct QPropertyBindingDataPointer;
friend class QPropertyBindingPrivate;
template<ObserverTag>
friend struct QPropertyObserverNodeProtector;
}; };
template <typename Functor> template <typename Functor>

View File

@ -245,13 +245,6 @@ public:
void clearDependencyObservers() { void clearDependencyObservers() {
for (size_t i = 0; i < qMin(dependencyObserverCount, inlineDependencyObservers.size()); ++i) { for (size_t i = 0; i < qMin(dependencyObserverCount, inlineDependencyObservers.size()); ++i) {
QPropertyObserverPointer p{&inlineDependencyObservers[i]}; QPropertyObserverPointer p{&inlineDependencyObservers[i]};
if (p.ptr->next.tag() == QPropertyObserver::ActivelyExecuting) {
*(p.ptr->nodeState) = nullptr;
p.ptr->nodeState = nullptr;
// set tag to "safer" value, as we return the same observer pointer from allocateDependencyObserver
p.ptr->next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler);
}
p.unlink(); p.unlink();
} }
if (heapObservers) if (heapObservers)