问题仅出于教育目的:
在两个对象(例如两个线程)之间使用30-50个或更多对信号和插槽是否会影响应用程序性能,运行时或响应时间?
答案 0 :(得分:61)
首先,你应该不在QThreads中添加任何插槽。除了重新实现run
方法和私有方法(不是信号!)之外,QThreads并不是真正意义上的。
QThread在概念上是一个线程控制器,而不是一个线程本身。在大多数情况下,您应该处理QObjects。启动一个线程,然后将对象实例移动到该线程。这是你在线程中正确使用插槽的唯一方法。将线程实例(是 QObject派生的!)移动到线程是一种黑客和糟糕的风格。尽管有不知情的论坛帖子,否则不要这样做。
关于你的其余问题:信号槽调用不必定位任何东西也不需要验证太多。建立连接时完成“位置”和“验证”。通话时的主要步骤是:
从池中锁定信号槽互斥锁。
迭代连接列表。
使用直接或排队呼叫执行呼叫。
常见费用
任何信号槽调用始终作为moc生成的信号实现中的直接调用启动。在堆栈上构造信号的指向参数的数组。参数不会被复制。
然后信号调用QMetaObject::activate
,其中获取连接列表互斥锁,并且迭代连接的插槽列表,为每个插槽发出呼叫。
直接连接
在那里做的不多,通过直接调用建立连接时获得的QObject::qt_static_metacall
来调用插槽,或者QObject::qt_metacall
如果使用QMetaObject::connect
来建立连接。后者允许dynamic creation of signals and slots。
排队连接
必须对参数进行编组和复制,因为调用必须存储在事件队列中并且信号必须返回。这是通过为复制分配一个指针数组,并在堆上复制每个参数来完成的。这样做的代码实际上是简单的旧C。
呼叫的排队是在queued_activate
内完成的。这是复制构造的地方。
排队调用的开销始终至少为QMetaCallEvent
的一个堆分配。如果调用有任何参数,则分配指向参数的数组,并为每个参数进行额外的分配。对于具有n
参数的调用,作为C表达式给出的成本是(n ? 2+n : 1)
分配。阻塞调用的返回值是counter作为参数。可以说,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.
或许应该注意的是,连接字符串非常快:)
请注意,我正在通过函数指针进行调用,这是为了防止编译器优化对add函数的直接调用。
//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"
答案 1 :(得分:5)
当然它们影响应用程序性能,主要是由于花在定位连接对象上的时间+验证槽对象状态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.
与回调相比,信号和插槽稍微慢一点,因为它们提供了更高的灵活性,尽管实际应用的差异微不足道。通常,发射连接到某些插槽的信号比使用非虚函数调用直接调用接收器慢大约十倍。这是定位连接对象,安全地遍历所有连接(即检查后续接收器在发射期间没有被破坏)以及以通用方式编组任何参数所需的开销。虽然十个非虚函数调用可能听起来很多,但它比任何新的或删除操作的开销要小得多,例如。一旦你执行一个字符串,向量或列表操作,在场景后面需要新的或删除,信号和插槽开销只负责一个非常小的 完整的函数调用成本的比例。