Подтвердить что ты не робот

Большее использование сигналов и слотов влияет на производительность приложения?

Вопрос только для образовательной цели:

Влияет ли использование 30-50 или более пар сигналов и слотов между двумя объектами (например, двумя потоками) на производительность приложения, время выполнения или время ответа?

4b9b3361

Ответ 1

Прежде всего, вы, вероятно, не должны помещать какие-либо слоты в QThreads. QThreads на самом деле не предназначен для того, чтобы быть полученным из чего-либо, кроме как путем переопределения метода run и приватных методов (не сигналов!).

Концептуально QThread - это контроллер потоков, а не сам поток. В большинстве случаев вы должны иметь дело с QObjects. Запустите поток, затем переместите экземпляр объекта в этот поток. Это единственный способ получить правильную работу слотов в потоке. Перемещение экземпляра потока (он является производным от QObject!) В поток - это хак и плохой стиль. Не делайте этого, несмотря на неинформированные сообщения на форуме, говорящие иначе.

Что касается остальной части вашего вопроса: вызов слота сигнала не должен ни находить, ни проверять. "Расположение" и "проверка" выполняется, когда соединение установлено. Основные шаги, сделанные во время звонка:

  1. Блокировка мьютекса сигнального слота из пула.

  2. Итерация по списку соединений.

  3. Выполнение звонков с использованием прямых или поставленных в очередь звонков.

Общая стоимость

Любой вызов сигнального слота всегда начинается как прямой вызов в реализации сигнала, генерируемой 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"

Ответ 2

Конечно, они влияют на производительность приложений, в основном из-за времени, затраченного на поиск объекта соединения +, подтверждение состояния состояния слота 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.

По сравнению с обратными вызовами сигналы и слоты немного медленнее из-за большей гибкости, которую они обеспечивают, хотя разница для реальных приложений незначительна. В общем случае, излучение сигнала, подключенного к некоторым слотам, примерно в десять раз медленнее, чем вызов приемников напрямую, с не виртуальными вызовами функций. Это накладные расходы, необходимые для поиска объекта соединения, для безопасной итерации по всем соединениям (т.е. Проверки того, что последующие приемники не были уничтожены во время эмиссии) и для сортировки любых параметров в общем виде. В то время как десять вызовов не виртуальных функций могут звучать как много, это намного меньше накладных расходов, чем любая новая операция или операция удаления, например. Как только вы выполняете операцию с строкой, вектором или списком, которая за сценой требует новых или удаленных, накладные расходы на сигналы и слоты несет ответственность только за очень маленькую доля от полной стоимости вызова функции.

Источник: Сигналы и слоты