summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel/qobject.cpp
diff options
context:
space:
mode:
authorAurélien Brooke <aurelien@bahiasoft.fr>2025-05-23 20:19:43 +0200
committerAurélien Brooke <aurelien@bahiasoft.fr>2025-07-11 20:44:23 +0200
commitf6211c079fa000c0d46b7912341f014669fa628a (patch)
tree84fb4988d0e03d76edded4b6f1068d8e017b5985 /src/corelib/kernel/qobject.cpp
parent54204fc7943ef91aa9b665b1287b870680697db4 (diff)
QMetaCallEvent: avoid heap allocations for small arguments
Previously, all arguments for queued connections were heap-allocated via QMetaType::create(), even for small types like int or QObject*. This incurred many micro-allocations and led to inefficient cross-thread memory ownership. Additionally, argument copying was the responsibility of the caller after constructing the QMetaCallEvent, leading to code duplication and potential misuse. To fix this, this patch introduces QQueuedMetaCallEvent as a specialized subclass of QMetaCallEvent, used exclusively for queued signal-slot delivery. It supports in-place argument storage by preallocating space for up to 3 argument values (plus 5 pointers and types), enabling copy- construction directly within the event object for types that fit within a 3 × sizeof(void*) buffer and are suitably aligned. Heap allocation via QMetaType::create() is still used as a fallback for larger types. Legacy QMetaCallEvent constructors and the args() and types() accessors are marked deprecated and will be removed once the corresponding qtdeclarative patch eliminates their usage. The prealloc_ member, currently shared for transitional reasons, will also be moved into QQueuedMetaCallEvent as part of that cleanup. Amends b7d073e9905bf9812ba96cecdcf6871a95517d30. Change-Id: Icb6c1544de193b3fe6311fe76eaf00cf501d8977 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Diffstat (limited to 'src/corelib/kernel/qobject.cpp')
-rw-r--r--src/corelib/kernel/qobject.cpp154
1 files changed, 130 insertions, 24 deletions
diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp
index 888c71f5a49..d67ada64c45 100644
--- a/src/corelib/kernel/qobject.cpp
+++ b/src/corelib/kernel/qobject.cpp
@@ -599,14 +599,19 @@ QMetaCallEvent::QMetaCallEvent(QtPrivate::SlotObjUniquePtr slotO,
/*!
\internal
*/
+QMetaCallEvent::QMetaCallEvent(const QObject *sender, int signalId, Data &&data)
+ : QAbstractMetaCallEvent(sender, signalId),
+ d(std::move(data)),
+ prealloc_()
+{
+}
+
+/*!
+ \internal
+ */
QMetaCallEvent::~QMetaCallEvent()
{
if (d.nargs_) {
- QMetaType *t = types();
- for (int i = 0; i < d.nargs_; ++i) {
- if (t[i].isValid() && d.args_[i])
- t[i].destroy(d.args_[i]);
- }
if (reinterpret_cast<void *>(d.args_) != reinterpret_cast<void *>(prealloc_))
free(d.args_);
}
@@ -628,6 +633,115 @@ void QMetaCallEvent::placeMetaCall(QObject *object)
}
/*!
+ \internal
+
+ Constructs a QQueuedMetaCallEvent by copying the argument values using their meta-types.
+ */
+QQueuedMetaCallEvent::QQueuedMetaCallEvent(ushort method_offset, ushort method_relative,
+ QObjectPrivate::StaticMetaCallFunction callFunction,
+ const QObject *sender, int signalId, int argCount,
+ const QtPrivate::QMetaTypeInterface * const *argTypes,
+ const void * const *argValues)
+ : QMetaCallEvent(sender, signalId, {nullptr, nullptr, callFunction, argCount,
+ method_offset, method_relative})
+{
+ allocArgs();
+ copyArgValues(argCount, argTypes, argValues);
+}
+
+/*!
+ \internal
+
+ Constructs a QQueuedMetaCallEvent by copying the argument values using their meta-types.
+ */
+QQueuedMetaCallEvent::QQueuedMetaCallEvent(QtPrivate::QSlotObjectBase *slotObj,
+ const QObject *sender, int signalId, int argCount,
+ const QtPrivate::QMetaTypeInterface * const *argTypes,
+ const void * const *argValues)
+ : QMetaCallEvent(sender, signalId, {QtPrivate::SlotObjUniquePtr(slotObj), nullptr, nullptr, argCount,
+ 0, ushort(-1)})
+{
+ if (d.slotObj_)
+ d.slotObj_->ref();
+ allocArgs();
+ copyArgValues(argCount, argTypes, argValues);
+}
+
+/*!
+ \internal
+
+ Constructs a QQueuedMetaCallEvent by copying the argument values using their meta-types.
+ */
+QQueuedMetaCallEvent::QQueuedMetaCallEvent(QtPrivate::SlotObjUniquePtr slotObj,
+ const QObject *sender, int signalId, int argCount,
+ const QtPrivate::QMetaTypeInterface * const *argTypes,
+ const void * const *argValues)
+ : QMetaCallEvent(sender, signalId, {std::move(slotObj), nullptr, nullptr, argCount,
+ 0, ushort(-1)})
+{
+ allocArgs();
+ copyArgValues(argCount, argTypes, argValues);
+}
+
+/*!
+ \internal
+ */
+QQueuedMetaCallEvent::~QQueuedMetaCallEvent()
+{
+ const QMetaType *t = types();
+ int inplaceIndex = 0;
+ for (int i = 0; i < d.nargs_; ++i) {
+ if (t[i].isValid() && d.args_[i]) {
+ if (typeFitsInPlace(t[i]) && inplaceIndex < InplaceValuesCapacity) {
+ // Only destruct
+ void *where = &valuesPrealloc_[inplaceIndex++].storage;
+ t[i].destruct(where);
+ } else {
+ // Destruct and deallocate
+ t[i].destroy(d.args_[i]);
+ }
+ }
+ }
+}
+
+/*!
+ \internal
+ */
+inline void QQueuedMetaCallEvent::copyArgValues(int argCount, const QtPrivate::QMetaTypeInterface * const *argTypes,
+ const void * const *argValues)
+{
+ void **args = d.args_;
+ QMetaType *types = this->types();
+ int inplaceIndex = 0;
+
+ types[0] = QMetaType(); // return type
+ args[0] = nullptr; // return value pointer
+ // no return value
+
+ for (int n = 1; n < argCount; ++n) {
+ types[n] = QMetaType(argTypes[n]);
+ if (typeFitsInPlace(types[n]) && inplaceIndex < InplaceValuesCapacity) {
+ // Copy-construct in place
+ void *where = &valuesPrealloc_[inplaceIndex++].storage;
+ types[n].construct(where, argValues[n]);
+ args[n] = where;
+ } else {
+ // Allocate and copy-construct
+ args[n] = types[n].create(argValues[n]);
+ }
+ }
+}
+
+/*!
+ \internal
+ */
+inline bool QQueuedMetaCallEvent::typeFitsInPlace(const QMetaType type)
+{
+ return (q20::cmp_less_equal(type.sizeOf(), sizeof(ArgValueStorage)) &&
+ q20::cmp_less_equal(type.alignOf(), alignof(ArgValueStorage)));
+}
+
+/*!
\class QSignalBlocker
\brief Exception-safe wrapper around QObject::blockSignals().
\since 5.3
@@ -4069,26 +4183,19 @@ static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connect
SlotObjectGuard slotObjectGuard { c->isSlotObject ? c->slotObj : nullptr };
locker.unlock();
- QMetaCallEvent *ev = c->isSlotObject ?
- new QMetaCallEvent(c->slotObj, sender, signal, nargs) :
- new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal, nargs);
-
- void **args = ev->args();
- QMetaType *types = ev->types();
-
- types[0] = QMetaType(); // return type
- args[0] = nullptr; // return value
-
- if (nargs > 1) {
- for (int n = 1; n < nargs; ++n)
- types[n] = QMetaType(argumentTypes[n - 1]);
-
- for (int n = 1; n < nargs; ++n)
- args[n] = types[n].create(argv[n]);
+ QVarLengthArray<const QtPrivate::QMetaTypeInterface *> argTypes;
+ argTypes.emplace_back(nullptr); // return type
+ for (int n = 1; n < nargs; ++n) {
+ argTypes.emplace_back(QMetaType(argumentTypes[n - 1]).iface()); // convert type ids to QMetaTypeInterfaces
}
+ auto ev = c->isSlotObject ?
+ std::make_unique<QQueuedMetaCallEvent>(c->slotObj,
+ sender, signal, nargs, argTypes.data(), argv) :
+ std::make_unique<QQueuedMetaCallEvent>(c->method_offset, c->method_relative, c->callFunction,
+ sender, signal, nargs, argTypes.data(), argv);
+
if (c->isSingleShot && !QObjectPrivate::removeConnection(c)) {
- delete ev;
return;
}
@@ -4096,11 +4203,10 @@ static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connect
if (!c->isSingleShot && !c->receiver.loadRelaxed()) {
// the connection has been disconnected while we were unlocked
locker.unlock();
- delete ev;
return;
}
- QCoreApplication::postEvent(receiver, ev);
+ QCoreApplication::postEvent(receiver, ev.release());
}
template <bool callbacks_enabled>