Phänomen Performancesteigerung beim zeichnen mit addLine

Alles rund um die Programmierung mit Qt
Antworten
Paddel
Beiträge: 11
Registriert: 23. Dezember 2011 10:38

Phänomen Performancesteigerung beim zeichnen mit addLine

Beitrag von Paddel »

Servus Gemeinde,
ich hab ein kleines Problem und kann es mir nicht ganz erklären warum das so ist.

Zuerst mal meine Beschreibung des Programms:
Ich empfange in einem separaten Thread von einem Socket Daten (je Socket 20000 Koordinaten), die ich dann per Signal/Slot an den mainThread sende um sie in einer graphicsScene per addLine zu zeichnen.
Das passiert in mehreren Fenstern parallel per QStackedWidget.
Dies funktioniert soweit, obwohl es etwas schneller sein könnte. Der Flaschenhals ist eindeutig die zeichnerei im mainThread.
Als ich dann mal zufällig ein Qt-Beispiel offen hatte, stellte ich fest, dass die Geschwindigkeit des zeichnens um das 6-fache schneller war.
Nach etwas Debugging stellte ich fest, dass dies der Fall war, sobald ein Timer (20ms) lief. Also hab ich den in meinem Programm im mainThread eingebaut und siehe da, es war ebenfalls so schnell.
ABER: es wurde jetzt immer Schrittweise gezeichnet, also alle paar 100 Punkte. Dies wäre jetzt nicht so schlimm, aber der event im mainThread scheint jetzt ganz für den Timer reserviert zu sein, so dass ich jetzt nicht mehr in ein anderes Fenster während dem Zeichnen wechseln konnte, was vorher der Fall war.

Kann mir jemand erklären, was der Timer GENAU bewirkt, dass plötzlich schneller gezeichnet wird als zuvor.
Im Prinzip hat doch jeder Emfangsthread seine eigene EventLoop und läuft in einem separaten Thread und somit auch auf einem eigenen Prozessor, somit sollte doch der mainThread eh nur mit zeichnen beschäftigt zu sein.

hier ein paar Code-Fetzen:

Empfangsthread:

Code: Alles auswählen

void clientThread::run()
{
  tcpSocket = new QTcpSocket();
  if (!tcpSocket->setSocketDescriptor(socketDescriptor)) 
  {
    emit error(tcpSocket->error());
    return;
  }
  else
  {
    emit newConnected(threadCount); // threadCount eins weniger, da ein Array mit 0 anfängt
  }
  threadCount++; // Anzahl der connecteten Clients 1 hochzählen

  connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readData()),Qt::DirectConnection);   
  connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(discon()));

  exec(); // es wird ein eigener eventLoop gestartet für den thread
}

void clientThread::readData() // liest die Daten aus dem Buffer
{
  QString oneMessage;
  while (tcpSocket->canReadLine())
  {
    oneMessage = tcpSocket->readLine();
    oneMessage = oneMessage.left(oneMessage.length()-1); //ohne ENDLINE-Zeichen

    emit drawNow(oneMessage); // im main thread
    msleep(1); // thread wird kurz angehalten, damit gezeichnet werden kann (eventLoop ausgelöst!).

//qDebug() << oneMessage;
  }

Teil vom mainThread:

Code: Alles auswählen

void QtDisplay::newClient()
{
  //check for all pending new connections
  while(server->hasPendingConnections())
  {
    //get next connection
    socket = server->nextPendingConnection();
    thread = new clientThread(socket->socketDescriptor(), this);
    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
    connect(thread, SIGNAL(drawNow( QString)), this, SLOT(drawData(QString)));         
    thread->start( QThread::LowPriority);
  }
}

void QtDisplay::drawData(QString oneMessage)
{
  // das Trennzeichen ";" suchen, damit empfangener String zerpflückt werden kann
  QStringList list = oneMessage.split(";");
  QString canvasNameIN = list.at(0);
  QString Befehlstext = list.at(1);
  QString canvasName = canvasNameIN.right(canvasNameIN.length()-1); //ohne "/"-Trennzeichen

  // passendes Handle zum Canvas-Fenster finden
  mainCanvas *currentCanvas = pagesWidget->findChild<mainCanvas *>(canvasName);

  // Parser 
  if (Befehlstext == "moveTo")
  {
    QString X1 = list.at(2);
    QString Y1 = list.at(3);
    double X1Value = X1.toDouble(); // umwandeln von String in Double
    double Y1Value = Y1.toDouble(); // umwandeln von String in Double
    canvasObj.QtMoveTo( currentCanvas->kontextPntr, X1Value, Y1Value );
  }

  if (Befehlstext == "LineTo") 
  {
    QString X1 = list.at(2);
    QString Y1 = list.at(3);
    double X1Value = X1.toDouble(); // umwandeln von String in Double
    double Y1Value = Y1.toDouble(); // umwandeln von String in Double

    canvasObj.QtLineTo( currentCanvas->kontextPntr, X1Value, Y1Value );
  }

}
Danke soweit.

Greetz, Paddel
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Re: Phänomen Performancesteigerung beim zeichnen mit addLine

Beitrag von franzf »

Der Thread ist sinnlos: Sockets (speziell so wie du sie verwendest) laufen schon asynchron. Was wirklich Sinn machen würde, wäre das ganze String-Parsen in einen Thread auszulagern, das drückt nämlich bei wirklich vielen Aufrufen sehr wohl auf die Performance. Außerdem würde ich sammeln, also nicht nach jeder geparsten Linie diese gleich hinzufügen.
Außerdem: Was hat das jetzt mit dem Timer zu tun? In deinem Code seh ich keinen...
Paddel
Beiträge: 11
Registriert: 23. Dezember 2011 10:38

Re: Phänomen Performancesteigerung beim zeichnen mit addLine

Beitrag von Paddel »

Hallo,
danke für den Hinweis zu den Sockets, werde mir das nochmal anschauen...
das Problem mit dem sammeln (welche Idee mir auch schon kam) ist diese, dass von mehreren Sockets gleichzeitig empfangen wird und der Parser von den verschiedenen Sockets ja auch in das richtige Fenster parsen und zeichnen lassen muss.
Ich kann zwar abprüfen ob mehrere gleiche Befehle hintereinander kommen, aber das stell ich mir schwierig vor, grad wegen den X-Sockets, die natürlich immer variieren können.
Aber ich schau mir das auch nochmal an, vielleicht etwas aufwendiger, da ich die Zeichenfunktion umbauen werde müssen...

das mit dem Timer hast du recht, hab ich ausversehen beim Posten gelöscht, da es zu testzwecke auskommentiert war, sorry.

hier nochmal:

Code: Alles auswählen

QtDisplay::QtDisplay()
{
  pagesWidget = new QStackedWidget;
  myCanvas1 = new mainCanvas(0,"myCanvas1");
  myCanvas1->setObjectName("canvas1"); // Objektname setzen für findChild
  myCanvas2 = new mainCanvas(0,"myCanvas2");
  myCanvas2->setObjectName("canvas2"); // Objektname setzen für findChild
  myCanvas3 = new canvas2;
  pagesWidget->addWidget(myCanvas1); // QWidget-Objekt der Klasse canvas1 dem pagesWidget hinzufügen
  pagesWidget->addWidget(myCanvas2); // QWidget-Objekt der Klasse canvas2 dem pagesWidget hinzufügen
  pagesWidget->addWidget(myCanvas3); 

  QPushButton *closeButton = new QPushButton(tr("Close"));
  QPushButton *deleteButton = new QPushButton(tr("Delete"));
  QPushButton *Canvas1Button = new QPushButton(tr("Canvas1"));
  QPushButton *Canvas2Button = new QPushButton(tr("Canvas2"));
  QPushButton *Canvas3Button = new QPushButton(tr("Canvas3"));

    connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));
  connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteCanvas()));
  connect(Canvas1Button, SIGNAL(clicked()), this, SLOT(changeCanvas1()));
  connect(Canvas2Button, SIGNAL(clicked()), this, SLOT(changeCanvas2()));
  connect(Canvas3Button, SIGNAL(clicked()), this, SLOT(changeCanvas3()));

  QHBoxLayout *buttonsLayout = new QHBoxLayout;
  buttonsLayout->addStretch(1); // ohne diese Zeile wird alles auf die gesamte Fläche aufgeteilt!
  buttonsLayout->addWidget(Canvas1Button);
  buttonsLayout->addWidget(Canvas2Button);
  buttonsLayout->addWidget(Canvas3Button);
  buttonsLayout->addWidget(deleteButton);
  buttonsLayout->addWidget(closeButton);

  QHBoxLayout *topLayout = new QHBoxLayout;
  topLayout->addWidget(pagesWidget,1);
  topLayout->addWidget(clientsBox);

  QVBoxLayout *mainLayout = new QVBoxLayout;
  mainLayout->addLayout(topLayout,1);
  mainLayout->addLayout(buttonsLayout);
  setLayout(mainLayout);

  // ######### SERVER anschmeißen ############################################################
  server = new QTcpServer(this);
  if (!server->listen(QHostAddress ("localhost"),20000)) 
  {
    QMessageBox::critical(this, tr("QtServer"),tr("Unable to start the server: %1.").arg(server->errorString()));
    close();
    return;
  }
  connect(server, SIGNAL(newConnection()), this, SLOT(newClient()));
  // ######### SERVER anschmeißen ENDE #######################################################

    QTimer *timer = new QTimer(this);
    timer->start(20);

}
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Re: Phänomen Performancesteigerung beim zeichnen mit addLine

Beitrag von franzf »

Dann bastel dir halt nen Thread, der von den ganzen Sockets die Daten sammelt, auswertet und verteilt. Lass dir was einfallen - irgend wer muss die Aufgabe erledigen, und Informationen müssen vorliegen, in welchem Fenster das Gezeichnete ankommen soll. Du kannst dir ja ein neues SIGNAL ausdenken, das entsprechend Informationen mitsendet:

Code: Alles auswählen

void newLinesAvailable(Target t, QList<QLine> lines);
wobei Target einfach eine eindeutige ID ist (sei es ein Pointer auf das Fenster, ein int, ein enum, ...)

Und das mit dem Timer kann ich mir nicht vorstellen. Der hängt frei in der Luft, läuft halt einfach mit, aber keine SLOTS hängen dran. Eigentlich DARF der nichts beeinflussen...
Paddel
Beiträge: 11
Registriert: 23. Dezember 2011 10:38

Re: Phänomen Performancesteigerung beim zeichnen mit addLine

Beitrag von Paddel »

OK, ich beherzige deine Ratschläge mal und werde es versuchen so anzupassen.
der Fenstername steckt in dem empfangenem String drin, daher ist es schon möglich, muss halt nur schauen, wie. :-)

und das mit dem Timer ist leider so und nachvollziehen kann ich es auch nicht.
Das merkwürde ist halt echt, dass es schneller läuft, sobald ein QT-Beispiel läuft, welches mit meinem Programm nichts zu tun hat. Es läuft einfach parallel auf dem Rechner mit.

Ich danke dir jedenfalls mal für deine kompetenten und ausführlichen Ratschläge.
Hab schon anderes erlebt...

Grüße, Paddel
Antworten