qt - signal - ¿El uso extenso de señales y ranuras afecta el rendimiento de la aplicación?
qt signals and slots tutorial (2)
Por supuesto, afectan el rendimiento de la aplicación, principalmente debido al tiempo invertido en localizar el objeto de conexión + validar el estado del objeto de la ranura n. Pero la simplicidad y la flexibilidad del mecanismo de las señales y las ranuras valen la pena. 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.
En comparación con las devoluciones de llamada, las señales y las ranuras son ligeramente más lentas debido a la mayor flexibilidad que proporcionan, aunque la diferencia para las aplicaciones reales es insignificante. En general, emitir una señal que está conectada a algunas ranuras es aproximadamente diez veces más lenta que llamar directamente a los receptores, con llamadas a funciones no virtuales. Esta es la sobrecarga requerida para ubicar el objeto de conexión, para iterar con seguridad sobre todas las conexiones (es decir, verificar que los receptores posteriores no se hayan destruido durante la emisión), y para ordenar cualquier parámetro de una manera genérica. Mientras que diez llamadas a funciones no virtuales pueden sonar mucho, es mucho menos sobrecarga que cualquier operación nueva o de eliminación, por ejemplo. Tan pronto como realice una operación de cadena, vector o lista que detrás de la escena requiera nueva o eliminar, las señales y ranuras de arriba solo son responsables de una proporción muy pequeña de los costos de llamadas a funciones completas.
Fuente: Señales y Tragamonedas
La pregunta acaba de hacerse con fines educativos:
¿El uso de 30-50 o más pares de señales y ranuras entre dos objetos (por ejemplo, dos hilos) afecta el rendimiento de la aplicación, el tiempo de ejecución o los tiempos de respuesta?
En primer lugar, probablemente no deberías poner ranuras en QThreads. QThreads en realidad no están destinados a ser derivados de otra manera que la reimplementación del método de run
y los métodos privados (¡no las señales!).
Un QThread es conceptualmente un controlador de subprocesos, no un subproceso en sí mismo. En la mayoría de los casos, debe tratar con QObjects. Comience un hilo, luego mueva la instancia del objeto a ese hilo. Esa es la única manera en que conseguirás que las máquinas tragamonedas funcionen correctamente en el hilo. Mover la instancia de subproceso ( es QObject-derived!) Al subproceso es un truco y un estilo incorrecto. No hagas eso a pesar de las publicaciones desinformadas en el foro que dicen lo contrario.
En cuanto al resto de su pregunta: una llamada de ranura de señal no tiene que localizar nada ni validar mucho. La "ubicación" y la "validación" se realizan cuando se establece la conexión. Los principales pasos realizados en el momento de la llamada son:
Bloquear un mutex de ranura de señal de un grupo.
Iterando a través de la lista de conexiones.
Realizando llamadas usando llamadas directas o en cola.
Costo común
Cualquier llamada de ranura de señal siempre comienza como una llamada directa en la implementación de la señal generada por moc. Se construye una matriz de punteros a argumentos de la señal en la pila. Los argumentos no se copian
La señal llama entonces a QMetaObject::activate
, donde se adquiere el mutex de la lista de conexiones y se itera la lista de ranuras conectadas, colocando la llamada para cada ranura.
Conexiones directas
No se hace mucho allí, se llama al intervalo llamando directamente a QObject::qt_static_metacall
obtenido en el momento en que se estableció la conexión, o QObject::qt_metacall
si se usó QMetaObject::connect
para configurar la conexión. Este último permite la creación dinámica de señales y ranuras .
Conexiones en cola
Los argumentos deben agruparse y copiarse, ya que la llamada debe almacenarse en una cola de eventos y la señal debe regresar. Esto se hace asignando una matriz de punteros a copias, y copiando consting cada argumento en el montón. El código para hacer eso es realmente sin adornos llanura C.
La queued_activate
en cola de la llamada se realiza dentro de queued_activate
. Aquí es donde se realiza la copia de construcción.
La sobrecarga de una llamada en cola es siempre al menos una asignación de montón de QMetaCallEvent
. Si la llamada tiene algún argumento, se asigna una matriz de punteros a argumentos y se realiza una asignación adicional para cada argumento. Para una llamada con n
argumentos, el costo dado como una expresión C es (n ? 2+n : 1)
asignaciones. Un valor de retorno para bloquear llamadas es contador como argumento. Podría decirse que este aspecto de Qt podría optimizarse hasta una asignación para todo, pero en la vida real solo importaría si está llamando a métodos triviales.
Resultados de referencia
Incluso una llamada de ranura de señal directa (sin cola) tiene una sobrecarga mensurable, pero debe elegir sus batallas. Facilidad para diseñar el código en comparación con el rendimiento. Mide el rendimiento de su aplicación final e identifica los cuellos de botella, ¿verdad? Si lo haces, es probable que veas que en las aplicaciones de la vida real, los gastos generales de la ranura de la señal no juegan ningún papel.
El único mecanismo de ranura de señal de tiempo tiene una sobrecarga significativa si está llamando a funciones triviales. Digamos, si llamas a la ranura trivial
en el siguiente código. Es un punto de referencia completo e independiente, así que siéntete libre de ejecutarlo y verlo por ti mismo. Los resultados en mi máquina fueron:
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.
Lo que debe tenerse en cuenta, tal vez, es que las cadenas de concatenación son bastante rápidas :)
Tenga en cuenta que estoy haciendo las llamadas a través de un puntero de función, esto es para evitar que el compilador optimice las llamadas directas a la función de adición.
//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 " << ts << "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"