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
@ -539,7 +502,7 @@ struct [[nodiscard]] QPropertyObserverNodeProtector {
ObserverNotifiesChangeHandler case would not work. Thus we instead pass the knowledge of ObserverNotifiesChangeHandler case would not work. Thus we instead pass the knowledge of
whether the value has changed we obtained when evaluating the binding eagerly along whether the value has changed we obtained when evaluating the binding eagerly along
*/ */
void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding, QUntypedPropertyData *propertyDataPtr,bool alreadyKnownToHaveChanged) void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding, QUntypedPropertyData *propertyDataPtr, bool alreadyKnownToHaveChanged)
{ {
bool knownIfPropertyChanged = alreadyKnownToHaveChanged; bool knownIfPropertyChanged = alreadyKnownToHaveChanged;
bool propertyChanged = true; bool propertyChanged = true;
@ -551,56 +514,56 @@ 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) { {
// both evaluateIfDirtyAndReturnTrueIfValueChanged and handlerToCall might modify the list auto handlerToCall = observer->changeHandler;
QPropertyObserverNodeProtector<QPropertyObserver::ObserverNotifiesChangeHandler> protector(observer); // prevent recursion
if (!knownIfPropertyChanged && triggeringBinding) { if (next && next->next.tag() == QPropertyObserver::ObserverIsPlaceholder) {
knownIfPropertyChanged = true; observer = next->next.data();
propertyChanged = triggeringBinding->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr); continue;
}
if (!propertyChanged)
return;
handlerToCall(observer, propertyDataPtr);
next = protector.m_placeHolder.next.data();
} else {
next = observer->next.data();
} }
// both evaluateIfDirtyAndReturnTrueIfValueChanged and handlerToCall might modify the list
QPropertyObserverNodeProtector protector(observer);
if (!knownIfPropertyChanged && triggeringBinding) {
knownIfPropertyChanged = true;
propertyChanged = triggeringBinding->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr);
}
if (!propertyChanged)
return;
handlerToCall(observer, propertyDataPtr);
next = protector.next();
break; break;
}
case QPropertyObserver::ObserverNotifiesBinding: case QPropertyObserver::ObserverNotifiesBinding:
if (auto bindingToMarkDirty = observer->bindingToMarkDirty) { {
QPropertyObserverNodeProtector<QPropertyObserver::ObserverNotifiesBinding> protector(observer); auto bindingToMarkDirty = observer->bindingToMarkDirty;
bindingToMarkDirty->markDirtyAndNotifyObservers(); QPropertyObserverNodeProtector protector(observer);
next = protector.m_placeHolder.next.data(); bindingToMarkDirty->markDirtyAndNotifyObservers();
} else { next = protector.next();
next = observer->next.data();
}
break; 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)