2012-05-31 12 views

Respuesta

55

En primer lugar, probablemente debería no poner ninguna ranura en QThreads. QThreads no están destinados a ser derivados de otro modo que la reimplementación del método run y métodos privados (¡no 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 encontrar 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:

  1. Bloqueo de un mutex de ranura de señal de un grupo.

  2. Iteración a través de la lista de conexiones.

  3. Realización de llamadas utilizando llamadas directas o en cola.

de costes comunes

Cualquier llamada ranura señal siempre comienza como una llamada directa en la aplicació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 al QMetaObject::activate, donde se adquiere la lista de conexiones mutex, y se itera la lista de ranuras conectadas, colocando la llamada para cada ranura.

conexiones directas

No hay mucho que se hace allí, la ranura se llama ya sea llamando directamente QObject::qt_static_metacall obtenido en el momento de establecerse la conexión, o si el QObject::qt_metacallQMetaObject::connect se utilizó para configurar la conexión. Este último permite dynamic creation of signals and slots.

conexiones en cola

Los argumentos que marshalled y copiado, ya que la llamada tiene que ser almacenado en una cola de eventos y la señal debe devolver. 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 antiguo C.

La puesta 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 argumentos n, 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 (no en cola) llamada ranura señal directa tiene una sobrecarga medible, pero hay que 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. Supongamos que llama al 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 la concatenación de cadenas es bastante rápido :)

Tenga en cuenta que estoy haciendo las llamadas a través de un puntero de función, esta es 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" 
+4

Sugiero que veas el tutorial muy claro sobre cómo usar definitivamente QThread http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the- full-explanation/ – linello

+0

Ella dice que es más o menos lo que digo aquí ... –

+0

Hay un error en el código anterior, la última prueba imprime el tiempo transcurrido (ts) de la prueba anterior. – Richy

4

Por supuesto que afectan el rendimiento de la aplicación, principalmente debido al tiempo dedicado a localizar el objeto de conexión + validar el estado del objeto de la ranura n así. Pero la simplicidad y flexibilidad del mecanismo de señales y ranuras bien vale 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 ofrecen, 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 de los costos completos de llamadas a función.

Fuente: Signals and Slots

+0

¿Estás seguro de que el factor de sobrecarga es tan bajo? Las señales llaman a QMetaObject :: activate, que tiene alrededor de cien líneas de código. Supongo que es unas 100 veces más lento que una llamada directa no virtual de la ranura. Pero estoy de acuerdo con usted: en la mayoría de los casos, estos gastos generales son insignificantes. – leemes

+0

La sobrecarga de una operación nueva o de eliminación en un asignador de memoria moderno en una aplicación de subproceso único es pequeña. Muy pequeña.¡De hecho, tan pequeño que concatenar dos QStrings de 1000 caracteres en un nuevo QString toma tanto tiempo como una sobrecarga de conexión de ranura de señal directa! –

Cuestiones relacionadas