aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/jsruntime/qv4referenceobject.cpp
diff options
context:
space:
mode:
authorOlivier De Cannière <olivier.decanniere@qt.io>2025-10-15 11:29:57 +0200
committerOlivier De Cannière <olivier.decanniere@qt.io>2025-11-13 09:00:55 +0100
commitf33d72c73ad6ab54ddfa2f73e721e600b7a43702 (patch)
tree871c318344f300ea92b0cd5f975763a295484eab /src/qml/jsruntime/qv4referenceobject.cpp
parent869bf6ffc03484ddf10ba405b7345120510d434c (diff)
JSRuntime: Don't immediately connect reference objects
The change in a7349e6433d092398d76cafa5408753f06892cd7 reduced the number of readbacks of reference objects by using connections to set the dirty state only when necessary. Establishing connections, however, comes at a cost and this introduced a regression in the delegates_item_childrenRect QmlBench benchmark. The mitigation introduced in this patch is to delay creating the connection. When a reference object is created, it does not read or create a connection but is simply marked as dirty. Only on a subsequent read from within a different statement than the reference object's creation's statement, does a connection get created. This keeps the benefit of reducing unnecessary reads on objects that are used multiple times while not spending too much on creating connections. This brings back the benchmark to its original level of performance. The referenceObjectChainReadsBackAsRequiredBasedOnParentSignals test was changed to expect 8 reads instead of 4. Since all the accesses are part of the same statement, no connection is ever created and more reads are required than before. The referenceObjectDoesNotFetchWithoutNotifyEventDateObject test is reformatted to separate the assignment from the reads. Otherwise no connection is ever created. The referenceObjectDoesNotLeakAConnectionToTheDestroyedSignalOnANotifyBindable test can be updated to expect 0 connections. With this change, we only connect if the reference object is read again from a different statement. Amends a7349e6433d092398d76cafa5408753f06892cd7 Fixes: QTBUG-140757 Pick-to: 6.10 Change-Id: I5d02ec6266c51fbbe5f2e01405081dd5a167a833 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'src/qml/jsruntime/qv4referenceobject.cpp')
-rw-r--r--src/qml/jsruntime/qv4referenceobject.cpp70
1 files changed, 70 insertions, 0 deletions
diff --git a/src/qml/jsruntime/qv4referenceobject.cpp b/src/qml/jsruntime/qv4referenceobject.cpp
index 336b715ca0..93be59b212 100644
--- a/src/qml/jsruntime/qv4referenceobject.cpp
+++ b/src/qml/jsruntime/qv4referenceobject.cpp
@@ -550,6 +550,76 @@ void Heap::ReferenceObject::connectToBindable(QObject *obj, int property, QQmlEn
QObject::connect(obj, &QObject::destroyed, [this]() { setDirty(true); }));
}
+bool ReferenceObject::shouldConnect(Heap::ReferenceObject *ref)
+{
+ if (ref->isAlwaysDirty())
+ return false;
+
+ // We want to connect when we are reading the reference object from a statement that is not
+ // its creation statement. In other words, when first storing it in a variable and then
+ // reusing it, potentially multiple times. This ensures that the cost of the connection itself
+ // is only paid when the user separated the declaration from the usage in their code, in the
+ // hope that this catches more complex and frequent uses, like accesses in a loop, where
+ // unnecessary copies would impact performance more significantly, and not one time accesses.
+ if (CppStackFrame *frame = ref->internalClass->engine->currentStackFrame) {
+ const auto *refObject = static_cast<Heap::ReferenceObject *>(ref);
+ if (frame->v4Function && frame->v4Function == refObject->function()
+ && frame->statementNumber() == refObject->statementIndex()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void ReferenceObject::connect(Heap::ReferenceObject *ref)
+{
+ int property = ref->property();
+ Heap::Object *object = ref->object();
+
+ while (object
+ && object->internalClass->vtable->type != Managed::Type_V4QObjectWrapper
+ && object->internalClass->vtable->type != Managed::Type_QMLTypeWrapper)
+ {
+ const auto type = object->internalClass->vtable->type;
+ if (type != Managed::Type_V4ReferenceObject
+ && type != Managed::Type_V4Sequence
+ && type != Managed::Type_DateObject
+ && type != Managed::Type_QMLValueTypeWrapper) {
+ break;
+ }
+
+ property = static_cast<QV4::Heap::ReferenceObject *>(object)->property();
+ object = static_cast<QV4::Heap::ReferenceObject *>(object)->object();
+ }
+
+ const auto doConnect = [&](auto wrapper) {
+ // The object may be valid on the first read but not on the second, check for that.
+ if (QObject *obj = wrapper->object()) {
+ auto *refObject = static_cast<Heap::ReferenceObject *>(ref);
+
+ auto *qmlEngine = object->internalClass->engine->qmlEngine();
+ if (qmlEngine && obj->metaObject()->property(property).isBindable())
+ refObject->connectToBindable(obj, property, qmlEngine);
+ else if (qmlEngine && obj->metaObject()->property(property).hasNotifySignal())
+ refObject->connectToNotifySignal(obj, property, qmlEngine);
+ }
+ };
+
+ if (object && object->internalClass->vtable->type == Managed::Type_V4QObjectWrapper) {
+ doConnect(static_cast<QV4::Heap::QObjectWrapper *>(object));
+ } else if (object && object->internalClass->vtable->type == Managed::Type_QMLTypeWrapper) {
+ auto wrapper = static_cast<QV4::Heap::QQmlTypeWrapper *>(object);
+ Scope scope(object->internalClass->engine);
+ Scoped<QV4::QQmlTypeWrapper> scopedWrapper(scope, wrapper);
+ doConnect(scopedWrapper);
+ }
+
+ if (!ref->isConnected()) {
+ // Mark AlwaysDirty to prevent further connection failures on every read
+ ref->setAlwaysDirty(true);
+ }
+}
+
} // namespace QV4
QT_END_NAMESPACE