QSlotObjectBase: combat virtual function "bloat"
authorMarc Mutz <marc.mutz@kdab.com>
Fri, 10 Aug 2012 14:12:58 +0000 (16:12 +0200)
committerQt by Nokia <qt-info@nokia.com>
Wed, 15 Aug 2012 21:40:57 +0000 (23:40 +0200)
In C++, the compiler creates extra functions and data for classes
with virtual functions. This can lead to "virtual function bloat":
  http://www.boost.org/doc/libs/1_47_0/doc/html/function/misc.html#id1382504

This is especially true when the number of instances is of the same
order of magnitute as the number of derived classes, such as is
common with type erasure techniques.

One such case is the QSlotObjectBase hierarchy, which this patch
tackles.

The mechanics of this optimisation are simple: re-implement the
virtual function call mechanism by hand, with function pointers.

But we go one step further and collapse the vtable into a single
pointer to a function that implements all three currently-defined
operations, swtching on an 'int which' argument. This even allows
us to extend this in a BC way, should that become necessary later,
by adding a new Operation and using the void** argument to
transport arguments, if any.

This approach was inspired by:
  Ulrich Drepper: How To Write Shared Libraries, Section 2.4.4
  http://www.akkadia.org/drepper/dsohowto.pdf

Also move the QSlotObjectBase hierarchy out of QObject so as not
to export all the derived classes.

This was pointed out in review by Thiago.

Results (Linux amd64, GCC 4.8-pre -O2 -std=c++11, stripped):

  size tst_qobject*

     text    data     bss     dec     hex filename
   523275   21192      48  544515   84f03 tst_qobject (old)
   507343   13984      48  521375   7f49f tst_qobject (new)

  relinfo.pl tst_qobject*

   (old) tst_qobject: 473 relocations, 0 relative (0%), 240 PLT entries, 240 for local syms (100%), 0 users
   (new) tst_qobject: 323 relocations, 0 relative (0%), 238 PLT entries, 238 for local syms (100%), 0 users

Change-Id: I40ad4744dde8c5c29ef62ed2d82d4b1ede178510
Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
src/corelib/kernel/qobject.cpp
src/corelib/kernel/qobject.h
src/corelib/kernel/qobject_impl.h
src/corelib/kernel/qobject_p.h

index a00d528..6ac2987 100644 (file)
@@ -72,6 +72,11 @@ QT_BEGIN_NAMESPACE
 
 static int DIRECT_CONNECTION_ONLY = 0;
 
+struct QSlotObjectBaseDeleter { // for use with QScopedPointer<QSlotObjectBase,...>
+    static void cleanup(QtPrivate::QSlotObjectBase *slot) {
+        if (slot && !slot->ref.deref() ) slot->destroy();
+    }
+};
 static int *queuedConnectionTypes(const QList<QByteArray> &typeNames)
 {
     int *types = new int [typeNames.count() + 1];
@@ -432,7 +437,7 @@ QMetaCallEvent::QMetaCallEvent(ushort method_offset, ushort method_relative, QOb
 /*!
     \internal
  */
-QMetaCallEvent::QMetaCallEvent(QObject::QSlotObjectBase *slotO, const QObject *sender, int signalId,
+QMetaCallEvent::QMetaCallEvent(QtPrivate::QSlotObjectBase *slotO, const QObject *sender, int signalId,
                                int nargs, int *types, void **args, QSemaphore *semaphore)
     : QEvent(MetaCall), slotObj_(slotO), sender_(sender), signalId_(signalId),
       nargs_(nargs), types_(types), args_(args), semaphore_(semaphore),
@@ -460,7 +465,7 @@ QMetaCallEvent::~QMetaCallEvent()
         semaphore_->release();
 #endif
     if (slotObj_ && !slotObj_->ref.deref())
-        delete slotObj_;
+        slotObj_->destroy();
 }
 
 /*!
@@ -864,7 +869,7 @@ QObjectPrivate::Connection::~Connection()
             delete [] v;
     }
     if (isSlotObject && !slotObj->ref.deref())
-        delete slotObj;
+        slotObj->destroy();
 }
 
 
@@ -3412,7 +3417,8 @@ void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_i
             const QObjectPrivate::StaticMetaCallFunction callFunction = c->callFunction;
             const int method_relative = c->method_relative;
             if (c->isSlotObject) {
-                QExplicitlySharedDataPointer<QObject::QSlotObjectBase> obj(c->slotObj);
+                c->slotObj->ref.ref();
+                const QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);
                 locker.unlock();
                 obj->call(receiver, argv ? argv : empty_argv);
                 locker.relock();
@@ -4185,13 +4191,13 @@ void qDeleteInEventHandler(QObject *o)
  */
 QMetaObject::Connection QObject::connectImpl(const QObject *sender, void **signal,
                                              const QObject *receiver, void **slot,
-                                             QObject::QSlotObjectBase *slotObj, Qt::ConnectionType type,
+                                             QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
                                              const int *types, const QMetaObject *senderMetaObject)
 {
     if (!sender || !signal || !slotObj || !senderMetaObject) {
         qWarning("QObject::connect: invalid null parametter");
         if (slotObj && !slotObj->ref.deref())
-            delete slotObj;
+            slotObj->destroy();
         return QMetaObject::Connection();
     }
     int signal_index = -1;
@@ -4200,7 +4206,7 @@ QMetaObject::Connection QObject::connectImpl(const QObject *sender, void **signa
     if (signal_index < 0 || signal_index >= QMetaObjectPrivate::get(senderMetaObject)->signalCount) {
         qWarning("QObject::connect: signal not found in %s", senderMetaObject->className());
         if (!slotObj->ref.deref())
-            delete slotObj;
+            slotObj->destroy();
         return QMetaObject::Connection(0);
     }
     signal_index += QMetaObjectPrivate::signalOffset(senderMetaObject);
@@ -4220,7 +4226,7 @@ QMetaObject::Connection QObject::connectImpl(const QObject *sender, void **signa
             while (c2) {
                 if (c2->receiver == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) {
                     if (!slotObj->ref.deref())
-                        delete slotObj;
+                        slotObj->destroy();
                     return QMetaObject::Connection();
                 }
                 c2 = c2->nextConnectionList;
@@ -4412,16 +4418,6 @@ QMetaObject::Connection::~Connection()
     the signal or the slot, or if the arguments do not match.
  */
 
-QObject::QSlotObjectBase::~QSlotObjectBase()
-{
-}
-
-bool QObject::QSlotObjectBase::compare(void** )
-{
-    return false;
-}
-
-
 QT_END_NAMESPACE
 
 #include "moc_qobject.cpp"
index 1d2d6a6..ee15734 100644 (file)
@@ -246,7 +246,7 @@ public:
 
         return connectImpl(sender, reinterpret_cast<void **>(&signal),
                            receiver, reinterpret_cast<void **>(&slot),
-                           new QSlotObject<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
+                           new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
                                            typename SignalType::ReturnType>(slot),
                             type, types, &SignalType::Object::staticMetaObject);
     }
@@ -268,7 +268,7 @@ public:
                           "Return type of the slot is not compatible with the return type of the signal.");
 
         return connectImpl(sender, reinterpret_cast<void **>(&signal), sender, 0,
-                           new QStaticSlotObject<Func2,
+                           new QtPrivate::QStaticSlotObject<Func2,
                                                  typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
                                                  typename SignalType::ReturnType>(slot),
                            Qt::DirectConnection, 0, &SignalType::Object::staticMetaObject);
@@ -282,7 +282,7 @@ public:
         typedef QtPrivate::FunctionPointer<Func1> SignalType;
 
         return connectImpl(sender, reinterpret_cast<void **>(&signal), sender, 0,
-                           new QFunctorSlotObject<Func2, SignalType::ArgumentCount, typename SignalType::Arguments, typename SignalType::ReturnType>(slot),
+                           new QtPrivate::QFunctorSlotObject<Func2, SignalType::ArgumentCount, typename SignalType::Arguments, typename SignalType::ReturnType>(slot),
                            Qt::DirectConnection, 0, &SignalType::Object::staticMetaObject);
     }
 #endif //Q_QDOC
@@ -394,56 +394,10 @@ private:
     Q_DISABLE_COPY(QObject)
     Q_PRIVATE_SLOT(d_func(), void _q_reregisterTimers(void *))
 
-    private:
-    // internal base class (interface) containing functions required to call a slot managed by a pointer to function.
-    struct Q_CORE_EXPORT QSlotObjectBase {
-        QAtomicInt ref;
-        QSlotObjectBase() : ref(1) {}
-        virtual ~QSlotObjectBase();
-        virtual void call(QObject *receiver, void **a) = 0;
-        virtual bool compare(void **);
-    };
-    // implementation of QSlotObjectBase for which the slot is a pointer to member function of a QObject
-    // Args and R are the List of arguments and the returntype of the signal to which the slot is connected.
-    template<typename Func, typename Args, typename R> struct QSlotObject : QSlotObjectBase
-    {
-        typedef QtPrivate::FunctionPointer<Func> FuncType;
-        Func function;
-        QSlotObject(Func f) : function(f) {}
-        virtual void call(QObject *receiver, void **a) {
-            FuncType::template call<Args, R>(function, static_cast<typename FuncType::Object *>(receiver), a);
-        }
-        virtual bool compare(void **f) {
-            return *reinterpret_cast<Func *>(f) == function;
-        }
-    };
-    // implementation of QSlotObjectBase for which the slot is a static function
-    // Args and R are the List of arguments and the returntype of the signal to which the slot is connected.
-    template<typename Func, typename Args, typename R> struct QStaticSlotObject : QSlotObjectBase
-    {
-        typedef QtPrivate::FunctionPointer<Func> FuncType;
-        Func function;
-        QStaticSlotObject(Func f) : function(f) {}
-        virtual void call(QObject *receiver, void **a) {
-            FuncType::template call<Args, R>(function, receiver, a);
-        }
-    };
-    // implementation of QSlotObjectBase for which the slot is a functor (or lambda)
-    // N is the number of arguments
-    // Args and R are the List of arguments and the returntype of the signal to which the slot is connected.
-    template<typename Func, int N, typename Args, typename R> struct QFunctorSlotObject : QSlotObjectBase
-    {
-        typedef QtPrivate::Functor<Func, N> FuncType;
-        Func function;
-        QFunctorSlotObject(const Func &f) : function(f) {}
-        virtual void call(QObject *receiver, void **a) {
-            FuncType::template call<Args, R>(function, receiver, a);
-        }
-    };
-
+private:
     static QMetaObject::Connection connectImpl(const QObject *sender, void **signal,
                                                const QObject *receiver, void **slotPtr,
-                                               QSlotObjectBase *slot, Qt::ConnectionType type,
+                                               QtPrivate::QSlotObjectBase *slot, Qt::ConnectionType type,
                                                const int *types, const QMetaObject *senderMetaObject);
 
     static bool disconnectImpl(const QObject *sender, void **signal, const QObject *receiver, void **slot,
index e016002..eeb64db 100644 (file)
@@ -99,6 +99,104 @@ namespace QtPrivate {
     template <typename... Args> struct ConnectionTypes<List<Args...>, true>
     { static const int *types() { static const int t[sizeof...(Args) + 1] = { (QtPrivate::QMetaTypeIdHelper<Args>::qt_metatype_id())..., 0 }; return t; } };
 #endif
+
+    // internal base class (interface) containing functions required to call a slot managed by a pointer to function.
+    struct QSlotObjectBase {
+        QAtomicInt ref;
+        // don't use virtual functions here; we don't want the
+        // compiler to create tons of per-polymorphic-class stuff that
+        // we'll never need. We just use one function pointer.
+        enum Operation {
+            Destroy,
+            Call,
+            Compare,
+
+            NumOperations
+        };
+        typedef bool (*ImplFn)(int which, QSlotObjectBase* this_, QObject *receiver, void **args);
+        const ImplFn impl;
+
+        explicit QSlotObjectBase(ImplFn fn) : ref(1), impl(fn) {} // ### make constexpr once QAtomicInt's ctor is, too
+        inline void destroy()                  { impl(Destroy, this, 0, 0); }
+        inline bool compare(void **a)   { return impl(Compare, this, 0, a); }
+        inline void call(QObject *r, void **a) { impl(Call,    this, r, a); }
+    protected:
+        ~QSlotObjectBase() {}
+    };
+    // implementation of QSlotObjectBase for which the slot is a pointer to member function of a QObject
+    // Args and R are the List of arguments and the returntype of the signal to which the slot is connected.
+    template<typename Func, typename Args, typename R> class QSlotObject : public QSlotObjectBase
+    {
+        typedef QtPrivate::FunctionPointer<Func> FuncType;
+        Func function;
+        static bool impl(int which, QSlotObjectBase *this_, QObject *r, void **a)
+        {
+            switch (which) {
+            case Destroy:
+                delete static_cast<QSlotObject*>(this_);
+                return true;
+            case Call:
+                FuncType::template call<Args, R>(static_cast<QSlotObject*>(this_)->function, static_cast<typename FuncType::Object *>(r), a);
+                return true;
+            case Compare:
+                return *reinterpret_cast<Func *>(a) == static_cast<QSlotObject*>(this_)->function;
+            case NumOperations: ;
+            }
+            return false;
+        }
+    public:
+        explicit QSlotObject(Func f) : QSlotObjectBase(&impl), function(f) {}
+    };
+    // implementation of QSlotObjectBase for which the slot is a static function
+    // Args and R are the List of arguments and the returntype of the signal to which the slot is connected.
+    template<typename Func, typename Args, typename R> class QStaticSlotObject : public QSlotObjectBase
+    {
+        typedef QtPrivate::FunctionPointer<Func> FuncType;
+        Func function;
+        static bool impl(int which, QSlotObjectBase *this_, QObject *r, void **a)
+        {
+            switch (which) {
+            case Destroy:
+                delete static_cast<QStaticSlotObject*>(this_);
+                return true;
+            case Call:
+                FuncType::template call<Args, R>(static_cast<QStaticSlotObject*>(this_)->function, r, a);
+                return true;
+            case Compare:
+                return false; // not implemented
+            case NumOperations: ;
+            }
+            return false;
+        }
+    public:
+        explicit QStaticSlotObject(Func f) : QSlotObjectBase(&impl), function(f) {}
+    };
+    // implementation of QSlotObjectBase for which the slot is a functor (or lambda)
+    // N is the number of arguments
+    // Args and R are the List of arguments and the returntype of the signal to which the slot is connected.
+    template<typename Func, int N, typename Args, typename R> class QFunctorSlotObject : public QSlotObjectBase
+    {
+        typedef QtPrivate::Functor<Func, N> FuncType;
+        Func function;
+        static bool impl(int which, QSlotObjectBase *this_, QObject *r, void **a)
+        {
+            switch (which) {
+            case Destroy:
+                delete static_cast<QFunctorSlotObject*>(this_);
+                return true;
+            case Call:
+                FuncType::template call<Args, R>(static_cast<QFunctorSlotObject*>(this_)->function, r, a);
+                return true;
+            case Compare:
+                return false; // not implemented
+            case NumOperations: ;
+            }
+            return false;
+        }
+    public:
+        explicit QFunctorSlotObject(const Func &f) : QSlotObjectBase(&impl), function(f) {}
+    };
+
 }
 
 
index 02580ca..d47426b 100644 (file)
@@ -120,7 +120,7 @@ public:
         QObject *receiver;
         union {
             StaticMetaCallFunction callFunction;
-            QObject::QSlotObjectBase *slotObj;
+            QtPrivate::QSlotObjectBase *slotObj;
         };
         // The next pointer for the singly-linked ConnectionList
         Connection *nextConnectionList;
@@ -280,7 +280,7 @@ public:
     /*! \internal
         \a signalId is in the signal index range (see QObjectPrivate::signalIndex()).
     */
-    QMetaCallEvent(QObject::QSlotObjectBase *slotObj, const QObject *sender, int signalId,
+    QMetaCallEvent(QtPrivate::QSlotObjectBase *slotObj, const QObject *sender, int signalId,
                    int nargs = 0, int *types = 0, void **args = 0, QSemaphore *semaphore = 0);
 
     ~QMetaCallEvent();
@@ -293,7 +293,7 @@ public:
     virtual void placeMetaCall(QObject *object);
 
 private:
-    QObject::QSlotObjectBase *slotObj_;
+    QtPrivate::QSlotObjectBase *slotObj_;
     const QObject *sender_;
     int signalId_;
     int nargs_;