大量使用信号和插槽会影响应用程序性能吗? [英] Does large use of signals and slots affect application performance?

查看:42
本文介绍了大量使用信号和插槽会影响应用程序性能吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题只是出于教育目的:

The question is just done for educational purpose:

在两个对象(例如两个线程)之间使用 30-50 对或更多信号槽对是否会影响应用程序性能、运行时间或响应时间?

Does the use of 30-50 or more pairs of signals and slots between two object (for example two threads) affect the application performance, runtime or response times?

推荐答案

首先,你应该不要在 QThreads 中放置任何插槽.除了重新实现 run 方法和私有方法(不是信号!)之外,QThreads 并不是真的要派生出来.

First of all, you should probably not put any slots in QThreads. QThreads aren't really meant to be derived from other than by reimplementing the run method and private methods (not signals!).

QThread 在概念上是一个线程控制器,而不是一个线程本身.在大多数情况下,您应该处理 QObjects.启动一个线程,然后将对象实例移动到该线程.这是让插槽在线程中正常工作的唯一方法.将线程实例(它 QObject 派生的!)移动到线程是一种黑客和糟糕的风格.尽管不知情的论坛帖子另有说法,但不要这样做.

A QThread is conceptually a thread controller, not a thread itself. In most cases you should deal with QObjects. Start a thread, then move the object instance to that thread. That's the only way you'll get slots working correctly in the thread. Moving the thread instance (it is QObject-derived!) to the thread is a hack and bad style. Don't do that in spite of uninformed forum posts telling otherwise.

至于您的其余问题:信号槽调用不必定位任何内容,也不必进行太多验证.位置"和验证"在建立连接时完成.通话时完成的主要步骤是:

As to the rest of your question: a signal-slot call does not have to locate anything nor validate much. The "location" and "validation" is done when the connection is established. The main steps done at the time of the call are:

  1. 从池中锁定信号槽互斥锁.

  1. Locking a signal-slot mutex from a pool.

遍历连接列表.

使用直接或排队呼叫执行呼叫.

Performing the calls using either direct or queued calls.

共同成本

任何信号槽调用总是作为 moc 生成的信号实现中的直接调用开始.信号的参数指针数组在堆栈上构造.参数不会被复制.

Any signal-slot call always starts as a direct call in the signal's implementation generated by moc. An array of pointers-to-arguments of the signal is constructed on the stack. The arguments are not copied.

信号然后调用QMetaObject::activate,获取连接列表互斥锁,迭代连接槽列表,对每个槽进行调用.

The signal then calls QMetaObject::activate, where the connection list mutex is acquired, and the list of connected slots is iterated, placing the call for each slot.

直接连接

那里没有做太多事情,通过直接调用在建立连接时获得的QObject::qt_static_metacallQObject::qt_metacall 来调用插槽,如果QMetaObject::connect 用于建立连接.后者允许动态创建信号和槽.

Not much is done there, the slot is called by either directly calling QObject::qt_static_metacall obtained at the time the connection was established, or QObject::qt_metacall if the QMetaObject::connect was used to setup the connection. The latter allows dynamic creation of signals and slots.

排队连接

必须对参数进行编组和复制,因为调用必须存储在事件队列中并且信号必须返回.这是通过分配一个指向副本的指针数组,并在堆上复制每个参数来完成的.执行此操作的代码确实是朴实无华的老式 C.

The arguments have to marshalled and copied, since the call has to be stored in an event queue and the signal must return. This is done by allocating an array of pointers to copies, and copy-consting each argument on the heap. The code to do that is really no-frills plain old C.

调用的排队是在queued_activate.这是复制构造完成的地方.

The queuing of the call is done within queued_activate. This is where the copy-construction is done.

排队调用的开销总是至少一个 QMetaCallEvent 的堆分配.如果调用有任何参数,则分配一个指向参数的指针数组,并为每个参数进行额外的分配.对于带有 n 参数的调用,作为 C 表达式给出的成本是 (n ? 2+n : 1) 分配.阻塞调用的返回值是 counter 作为参数.可以说,Qt 的这一方面可以优化为对所有内容进行一次分配,但在现实生活中,只有调用微不足道的方法才重要.

The overhead of a queued call is always at least one heap allocation of QMetaCallEvent. If the call has any arguments, then a pointers-to-arguments array is allocated, and an extra allocation is done for each argument. For a call with n arguments, the cost given as a C expression is (n ? 2+n : 1) allocations. A return value for blocking calls is counter as an argument. Arguably, this aspect of Qt could be optimized down to one allocation for everything, but in real life it'd only matter if you're calling trivial methods.

基准测试结果

即使是直接(非排队)信号槽调用也有可衡量的开销,但您必须选择战斗.轻松构建代码与性能.您确实测量了最终应用程序的性能并确定了瓶颈,是吗?如果这样做,您很可能会发现在实际应用中,信号时隙开销没有任何作用.

Even a direct (non-queued) signal-slot call has a measurable overhead, but you have to choose your battles. Ease of architecting the code vs. performance. You do measure performance of your final application and identify bottlenecks, do you? If you do, you're likely to see that in real-life applications, signal-slot overheads play no role.

唯一具有显着开销的时间信号槽机制是在调用微不足道的函数时.假设您在下面的代码中调用 trivial 槽.这是一个完整的、独立的基准测试,因此您可以随意运行它并亲自查看.我的机器上的结果是:

The only time signal-slot mechanism has significant overhead is if you're calling trivial functions. Say, if you'd call the trivial slot in the code below. It's a complete, stand-alone benchmark, so feel free to run it and see for yourself. The results on my machine were:

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.

也许应该注意的是,连接字符串非常快:)

What should be noted, perhaps, is that concatenating strings is quite fast :)

请注意,我通过函数指针进行调用,这是为了防止编译器优化对加法函数的直接调用.

Note that I'm doing the calls via a function pointer, this is to prevent the compiler from optimizing out the direct calls to the addition function.

//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"

这篇关于大量使用信号和插槽会影响应用程序性能吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆