QT signal is ten times slower than a virtual function

I defined a QT Object that contains an integer value and uses QT signal along with awl::Observable based on virtual functions to notify the subscribers when the value changes:

#include "Awl/Observable.h"

#include <QObject>

namespace test
{
    class NotifyValueChanged
    {
    public:

        virtual void onValueChanged() = 0;
    };
    
    class ValueObject : 
        public QObject,
        public awl::Observable<NotifyValueChanged>
    {
        Q_OBJECT

    public:

        Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)

        void notifyValueChanged()
        {
            Notify(&NotifyValueChanged::onValueChanged);
        }

    signals:

        void valueChanged();

    private:

        int value() const
        {
            return m_val;
        }

        void setValue(int val)
        {
            if (m_val != val)
            {
                m_val = val;

                emit valueChanged();
            }
        }

        int m_val = 0;
    };
}

Then I defined the subscriber (or observer) object:

namespace test
{
    class ObserverObject : 
        public QObject,
        public awl::Observer<NotifyValueChanged>
    {
        Q_OBJECT

    public:

        void onValueChanged() override
        {
            ++m_count;
        }

        void onValueChangedSignal()
        {
            ++m_count;
        }

        std::size_t count() const
        {
            return m_count;
        }

    private:

        std::size_t m_count = 0;
    };
}

Then I created 1000 observers and notified them 100000 times with both mechanisms:

using namespace test;

QTIL_UNIT_TEST(SignalTest)
{
    QTIL_ATTRIBUTE(size_t, observer_count, 1000);
    QTIL_ATTRIBUTE(size_t, emit_count, 100000);

    {
        ValueObject object;

        std::vector<ObserverObject> observers(observer_count);
        
        for (ObserverObject& observer : observers)
        {
            QObject::connect(&object, &ValueObject::valueChanged, &observer, &ObserverObject::onValueChangedSignal, Qt::DirectConnection);
        }

        {
            awl::StopWatch sw;

            for (size_t i = 0; i < emit_count; ++i)
            {
                emit object.valueChanged();
            }

            context.logger.debug(qtil::Format() << "QT signal: " << sw);
        }

        for (ObserverObject& observer : observers)
        {
            AWT_ASSERT(observer.count() == emit_count);
        }
    }

    {
        ValueObject object;

        std::vector<ObserverObject> observers(observer_count);

        for (ObserverObject& observer : observers)
        {
            object.Subscribe(&observer);
        }

        {
            awl::StopWatch sw;

            for (size_t i = 0; i < emit_count; ++i)
            {
                emit object.notifyValueChanged();
            }

            context.logger.debug(qtil::Format() << "Virtual function: " << sw);
        }
        for (ObserverObject& observer : observers)
        {
            AWT_ASSERT(observer.count() == emit_count);
        }
    }
}

If I compile this code with MSVC2022 Compiler Version 19.32.31332 for Windows 64bit and run the test ten times on a machine with Intel i7 CPU I get the following output:

QT signal: 00:00:01.841
Virtual function: 00:00:00.179

QT signal: 00:00:01.782
Virtual function: 00:00:00.185

QT signal: 00:00:01.761
Virtual function: 00:00:00.181

QT signal: 00:00:01.739
Virtual function: 00:00:00.187

QT signal: 00:00:01.752
Virtual function: 00:00:00.183

QT signal: 00:00:01.714
Virtual function: 00:00:00.179

QT signal: 00:00:01.715
Virtual function: 00:00:00.174

QT signal: 00:00:01.721
Virtual function: 00:00:00.173

QT signal: 00:00:01.726
Virtual function: 00:00:00.179

QT signal: 00:00:01.690
Virtual function: 00:00:00.175

MSVC Performance profiler displays this:

If I access the sender from ObserverObject as follows:

        void onValueChangedSignal()
        {
            sender();

            ++m_count;
        }

QT signal becomes 16 time slower than the virtual function:

QT signal: 00:00:03.060
Virtual function: 00:00:00.215

QT signal: 00:00:02.916
Virtual function: 00:00:00.181

QT signal: 00:00:02.945
Virtual function: 00:00:00.181

QT signal: 00:00:02.996
Virtual function: 00:00:00.187

QT signal: 00:00:03.031
Virtual function: 00:00:00.183

QT signal: 00:00:03.034
Virtual function: 00:00:00.185

QT signal: 00:00:03.011
Virtual function: 00:00:00.178

QT signal: 00:00:03.047
Virtual function: 00:00:00.187

QT signal: 00:00:03.071
Virtual function: 00:00:00.185

QT signal: 00:00:03.111
Virtual function: 00:00:00.186

Profiler displays this:

So per second I can call 558,659,217 virtual function, 59,171,597 signals without sender and 32,562,683 signals with sender (the formula for virtual functions is 1000 * 100000 * (1 / 0.179)). Assume I receive 100,000 trades per second from some crypo exchange and if one trade triggers 100 signals than I have 10.000.000 signals per second that is something comparable with the maximum.

See my next post where I show how QT signal befomes 57 times slow than a virtual function.

2 Responses to QT signal is ten times slower than a virtual function

  1. M. says:

    I knew signals in qt are fast, but I am blown away by how fast they actually are. Wow.

    1. dmitriano says:

      Are snails fast?

Leave a Reply

Your email address will not be published. Required fields are marked *