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.
I knew signals in qt are fast, but I am blown away by how fast they actually are. Wow.
Are snails fast?