Вопрос только для образовательной цели:
Влияет ли использование 30-50 или более пар сигналов и слотов между двумя объектами (например, двумя потоками) на производительность приложения, время выполнения или время ответа?
Вопрос только для образовательной цели:
Влияет ли использование 30-50 или более пар сигналов и слотов между двумя объектами (например, двумя потоками) на производительность приложения, время выполнения или время ответа?
Прежде всего, вы, вероятно, не должны помещать какие-либо слоты в QThreads. QThreads на самом деле не предназначен для того, чтобы быть полученным из чего-либо, кроме как путем переопределения метода run
и приватных методов (не сигналов!).
Концептуально QThread - это контроллер потоков, а не сам поток. В большинстве случаев вы должны иметь дело с QObjects. Запустите поток, затем переместите экземпляр объекта в этот поток. Это единственный способ получить правильную работу слотов в потоке. Перемещение экземпляра потока (он является производным от QObject!) В поток - это хак и плохой стиль. Не делайте этого, несмотря на неинформированные сообщения на форуме, говорящие иначе.
Что касается остальной части вашего вопроса: вызов слота сигнала не должен ни находить, ни проверять. "Расположение" и "проверка" выполняется, когда соединение установлено. Основные шаги, сделанные во время звонка:
Блокировка мьютекса сигнального слота из пула.
Итерация по списку соединений.
Выполнение звонков с использованием прямых или поставленных в очередь звонков.
Общая стоимость
Любой вызов сигнального слота всегда начинается как прямой вызов в реализации сигнала, генерируемой moc. Массив указателей на аргументы сигнала строится в стеке. Аргументы не копируются.
Затем сигнал вызывает QMetaObject::activate
, где получается мьютекс списка соединений, и список подключенных слотов итерируется, выполняя вызов для каждого слота.
Прямые Связи
Там мало что делается, слот вызывается либо прямым вызовом QObject::qt_static_metacall
полученным во время установления соединения, либо QObject::qt_metacall
если QMetaObject::connect
использовался для установки соединения. Последнее позволяет динамически создавать сигналы и слоты.
Подключения в очереди
Аргументы должны быть собраны и скопированы, поскольку вызов должен быть сохранен в очереди событий, а сигнал должен вернуться. Это делается путем выделения массива указателей для копий и ограничения каждого аргумента в куче. Код для этого действительно простой старый C.
queued_activate
вызова выполняется в рамках queued_activate
. Это где копирование-строительство сделано.
QMetaCallEvent
на вызов в очереди всегда составляют как минимум одно выделение кучи QMetaCallEvent
. Если у вызова есть какие-либо аргументы, то выделяется массив указателей на аргументы, и для каждого аргумента выполняется дополнительное выделение. Для вызова с n
аргументами стоимость, указанная в виде выражения C, составляет (n? 2+n: 1)
выделения. Возвращаемое значение для блокировки вызовов является счетчиком в качестве аргумента. Возможно, этот аспект Qt можно оптимизировать до одного распределения для всего, но в реальной жизни это будет иметь значение, только если вы вызываете тривиальные методы.
Результаты тестов
Даже прямой (не поставленный в очередь) вызов в слоте сигнала имеет измеримые накладные расходы, но вы должны выбрать свои битвы. Простота архитектуры кода в сравнении с производительностью. Вы измеряете производительность вашего окончательного заявления и выявляете узкие места, не так ли? Если вы это сделаете, вы, скорее всего, увидите, что в реальных приложениях издержки сигнального слота не играют никакой роли.
Единственный механизм временного слота сигнала имеет значительные накладные расходы, если вы вызываете тривиальные функции. Скажем, если вы назвали trivial
слот в коде ниже. Это полный, автономный тест, так что не стесняйтесь запустить его и убедитесь сами. Результаты на моей машине были:
Warming up the caches...
trivial direct call took 3ms
nonTrivial direct call took 376ms
trivial direct signal-slot call took 158ms, 5166% longer than direct call.
nonTrivial direct signal-slot call took 548ms, 45% longer than direct call.
trivial queued signal-slot call took 2474ms, 1465% longer than direct signal-slot and 82366% longer than direct call.
nonTrivial queued signal-slot call took 2474ms, 416% longer than direct signal-slot and 653% longer than direct call.
Что следует отметить, возможно, это то, что объединение строк довольно быстро :)
Обратите внимание, что я делаю вызовы через указатель на функцию, чтобы не допустить оптимизации компилятором прямых вызовов функции сложения.
//main.cpp
#include <cstdio>
#include <QCoreApplication>
#include <QObject>
#include <QTimer>
#include <QElapsedTimer>
#include <QTextStream>
static const int n = 1000000;
class Test : public QObject
{
Q_OBJECT
public slots:
void trivial(int*, int, int);
void nonTrivial(QString*, const QString&, const QString&);
signals:
void trivialSignalD(int*, int, int);
void nonTrivialSignalD(QString*, const QString&, const QString &);
void trivialSignalQ(int*, int, int);
void nonTrivialSignalQ(QString*, const QString&, const QString &);
private slots:
void run();
private:
void benchmark(bool timed);
void testTrivial(void (Test::*)(int*,int,int));
void testNonTrivial(void (Test::*)(QString*,const QString&, const QString&));
public:
Test();
};
Test::Test()
{
connect(this, SIGNAL(trivialSignalD(int*,int,int)),
SLOT(trivial(int*,int,int)), Qt::DirectConnection);
connect(this, SIGNAL(nonTrivialSignalD(QString*,QString,QString)),
SLOT(nonTrivial(QString*,QString,QString)), Qt::DirectConnection);
connect(this, SIGNAL(trivialSignalQ(int*,int,int)),
SLOT(trivial(int*,int,int)), Qt::QueuedConnection);
connect(this, SIGNAL(nonTrivialSignalQ(QString*,QString,QString)),
SLOT(nonTrivial(QString*,QString,QString)), Qt::QueuedConnection);
QTimer::singleShot(100, this, SLOT(run()));
}
void Test::run()
{
// warm up the caches
benchmark(false);
// do the benchmark
benchmark(true);
}
void Test::trivial(int * c, int a, int b)
{
*c = a + b;
}
void Test::nonTrivial(QString * c, const QString & a, const QString & b)
{
*c = a + b;
}
void Test::testTrivial(void (Test::* method)(int*,int,int))
{
static int c;
int a = 1, b = 2;
for (int i = 0; i < n; ++i) {
(this->*method)(&c, a, b);
}
}
void Test::testNonTrivial(void (Test::* method)(QString*, const QString&, const QString&))
{
static QString c;
QString a(500, 'a');
QString b(500, 'b');
for (int i = 0; i < n; ++i) {
(this->*method)(&c, a, b);
}
}
static int pct(int a, int b)
{
return (100.0*a/b) - 100.0;
}
void Test::benchmark(bool timed)
{
const QEventLoop::ProcessEventsFlags evFlags =
QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers;
QTextStream out(stdout);
QElapsedTimer timer;
quint64 t, nt, td, ntd, ts, nts;
if (!timed) out << "Warming up the caches..." << endl;
timer.start();
testTrivial(&Test::trivial);
t = timer.elapsed();
if (timed) out << "trivial direct call took " << t << "ms" << endl;
timer.start();
testNonTrivial(&Test::nonTrivial);
nt = timer.elapsed();
if (timed) out << "nonTrivial direct call took " << nt << "ms" << endl;
QCoreApplication::processEvents(evFlags);
timer.start();
testTrivial(&Test::trivialSignalD);
QCoreApplication::processEvents(evFlags);
td = timer.elapsed();
if (timed) {
out << "trivial direct signal-slot call took " << td << "ms, "
<< pct(td, t) << "% longer than direct call." << endl;
}
timer.start();
testNonTrivial(&Test::nonTrivialSignalD);
QCoreApplication::processEvents(evFlags);
ntd = timer.elapsed();
if (timed) {
out << "nonTrivial direct signal-slot call took " << ntd << "ms, "
<< pct(ntd, nt) << "% longer than direct call." << endl;
}
timer.start();
testTrivial(&Test::trivialSignalQ);
QCoreApplication::processEvents(evFlags);
ts = timer.elapsed();
if (timed) {
out << "trivial queued signal-slot call took " << ts << "ms, "
<< pct(ts, td) << "% longer than direct signal-slot and "
<< pct(ts, t) << "% longer than direct call." << endl;
}
timer.start();
testNonTrivial(&Test::nonTrivialSignalQ);
QCoreApplication::processEvents(evFlags);
nts = timer.elapsed();
if (timed) {
out << "nonTrivial queued signal-slot call took " << nts << "ms, "
<< pct(nts, ntd) << "% longer than direct signal-slot and "
<< pct(nts, nt) << "% longer than direct call." << endl;
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Test t;
return a.exec();
}
#include "main.moc"
Конечно, они влияют на производительность приложений, в основном из-за времени, затраченного на поиск объекта соединения +, подтверждение состояния состояния слота n таким образом. Но простота и гибкость механизма сигналов и слотов хорошо стоят накладных расходов. Plus one of the major advantage of signal-slot mechanism is they are type=safe allowing communication between objects, irrespective of type of object unlike callbacks.
По сравнению с обратными вызовами сигналы и слоты немного медленнее из-за большей гибкости, которую они обеспечивают, хотя разница для реальных приложений незначительна. В общем случае, излучение сигнала, подключенного к некоторым слотам, примерно в десять раз медленнее, чем вызов приемников напрямую, с не виртуальными вызовами функций. Это накладные расходы, необходимые для поиска объекта соединения, для безопасной итерации по всем соединениям (т.е. Проверки того, что последующие приемники не были уничтожены во время эмиссии) и для сортировки любых параметров в общем виде. В то время как десять вызовов не виртуальных функций могут звучать как много, это намного меньше накладных расходов, чем любая новая операция или операция удаления, например. Как только вы выполняете операцию с строкой, вектором или списком, которая за сценой требует новых или удаленных, накладные расходы на сигналы и слоты несет ответственность только за очень маленькую доля от полной стоимости вызова функции.
Источник: Сигналы и слоты