Kann Code, der in einem Hintergrundthread ausgeführt wird, schneller sein als im Haupt-VCL-Thread in Delphi?

8

Wenn jemand viel Erfahrung mit Timing-Code hatte, der auf dem VCL-Hauptthread läuft, würde ich gerne eine Meinung dazu bekommen. Ich habe etwas Code, der in meiner Delphi 6-Anwendung auf dem Hauptthread einige schwere String-Verarbeitung ausführt. Jedes Mal, wenn ich eine Operation starte, schwebt die Zeit für jede Operation um 50 ms an einem einzelnen Thread auf meinem i5 Quad Core. Was mich wirklich misstrauisch macht ist, dass der selbe Code, der auf einem alten Pentium 4 läuft, dieselbe Zeit für die Operation anzeigt, wenn ich normalerweise sehe, dass Code auf dem Pentium 4 etwa 4 mal langsamer läuft als der Quad Core. Ich beginne mich zu fragen, ob der Code wesentlich weniger Zeit als 50 ms verbrauchen würde, aber dass etwas an dem Haupt-VCL-Thread, vielleicht Windows-Nachrichtenbehandlung oder Windows API-Aufrufe, ein künstliches "Floor" für den Vorgang erzeugt. Beachten Sie, dass eine Operation durch eine eingehende Anfrage an einem Socket ausgelöst wird, wenn dies von Bedeutung ist, die Zeitmessung jedoch erst stattfindet, wenn die Daten vollständig empfangen wurden.

Bevor ich den gesamten Code zum Testen auf einen Hintergrund-Thread verschiebe, frage ich mich, ob jemand in diesem Bereich allgemeines Wissen hat? Was waren Ihre Erfahrungen mit Code, der auf dem Haupt-VCL-Thread ausgeführt wird? Beachten Sie, dass die Timing-Messungen durchgeführt werden, wenn während der Tests absolut keine vom Benutzer ausgelöste Aktivität stattfindet.

Ich frage mich auch, ob es etwas bringen würde, die Priorität des Threads auf knapp unter Echtzeit zu erhöhen. Ich habe noch nie eine Verbesserung meiner Laufzeiten beim Experimentieren mit diesen Flags gesehen.

- roschler

    
Robert Oschler 19.07.2011, 03:54
quelle

5 Antworten

10

Ohne einfachen Quellcode, um das Problem zu reproduzieren und wie Sie Ihre Threads zeitlich festlegen, ist es schwierig zu verstehen, was in Ihrer Software vorkommt.

Klingt definitiv wie:

  • Ein Architekturproblem - wie sind Ihre Themen definiert?
  • Ein Messproblem - wie messen Sie Ihre Threads?
  • Ein typisches Skalierungsproblem des Speichermanagers und der RTL-String-bezogenen Implementierung.

Über den letzten Punkt, bedenken Sie dies:

  • Der aktuelle Speichermanager (FastMM4) skaliert nicht gut auf Multicore-CPU; Probieren Sie es mit einem Pro-Thread-Speichermanager aus, wie z. B. unserem experimentellen SynScaleMM - Hinweis z. dass das Free Pascal Compiler-Team kürzlich eine neue Skalierungs-MM geschrieben hat, um ein solches Problem zu vermeiden;
  • Versuchen Sie, die String-Prozessimplementierung zu ändern, um Speicherzuweisungen zu vermeiden (statische Puffer zu verwenden) und String-Reference-Counting (jeder String-Reference-Count-Zugriff erzeugt LOCK DEC/INC , die auf Multi-Code-CPU nicht so gut skaliert thread char-level process, wobei zB PChar für statische Puffer anstelle von string ) verwendet wird.

Ich bin sicher, dass Sie ohne string -Operationen feststellen werden, dass alle Threads äquivalent sind.

Kurz gesagt: weder die aktuelle Delphi MM, noch die aktuelle String-Implementierung skaliert gut auf Multicore-CPU. Sie haben gerade ein bekanntes Problem der aktuellen RTL herausgefunden. Lesen Sie diese SO-Frage .

    
Arnaud Bouchez 19.07.2011, 06:08
quelle
12

Wenn alle Threads die gleiche Priorität haben, wie sie es normalerweise tun, kann es aus folgenden Gründen keinen Unterschied geben. Wenn Sie einen Unterschied feststellen, überprüfen Sie den Code erneut (stellen Sie sicher, dass Sie dasselbe in VCL- und Hintergrundthreads ausführen), und stellen Sie sicher, dass Sie die Zeit richtig einstellen:

  • Der Compiler generiert genau den gleichen Code, es ist egal, ob der Code im Hauptthread oder in einem Hintergrundthread ausgeführt wird. Tatsächlich können Sie den gesamten Code in eine Prozedur einfügen und diese sowohl vom Execute() des Worker-Threads als auch vom VCL-Hauptthread aufrufen.

  • Für die CPU sind alle Kerne und alle Threads gleich. Es sei denn, es handelt sich tatsächlich um eine Hyperthreading-CPU, bei der nicht alle Kerne real sind, aber dann die nächste Kugel.

  • Auch wenn nicht alle CPU - Kerne gleich sind, wird Ihr Thread sehr unwahrscheinlich auf demselben Kern laufen , das Betriebssystem kann sie beliebig verschieben (und plant tatsächlich Ihren Thread zu verschiedenen Zeiten auf verschiedenen Kernen laufen).

  • Der Messaging-Overhead ist für den VCL-Hauptthread nicht von Bedeutung, denn wenn Sie Application.ProcessMessages() nicht manuell aufrufen, wird der Message-Pump einfach gestoppt, während Ihre Prozedur funktioniert. Die Nachrichtenpumpe ist passiv, Ihr Thread muss Nachrichten von der Warteschlange anfordern, aber da der Thread beschäftigt ist, Ihre Arbeit zu erledigen, fordert er keine Nachrichten an, also keinen Overhead.

Es gibt nur eine Stelle, an der Threads nicht gleich sind, und dies kann die wahrgenommene Ausführungsgeschwindigkeit ändern: Es ist das Betriebssystem, das Threads für Ausführungseinheiten (Kerne) plant und für die Betriebssystem-Threads unterschiedliche Prioritäten hat. Sie können dem Betriebssystem mitteilen, dass ein bestimmter Thread mithilfe des % anders behandelt werden muss. co_de% API (die von der SetThreadPriority() -Eigenschaft verwendet wird).

    
Cosmin Prund 19.07.2011 05:26
quelle
6
___ qstnhdr ___ Kann Code, der in einem Hintergrundthread ausgeführt wird, schneller sein als im Haupt-VCL-Thread in Delphi? ___ answer6742952 ___

Ohne einfachen Quellcode, um das Problem zu reproduzieren und wie Sie Ihre Threads zeitlich festlegen, ist es schwierig zu verstehen, was in Ihrer Software vorkommt.

Klingt definitiv wie:

  • Ein Architekturproblem - wie sind Ihre Themen definiert?
  • Ein Messproblem - wie messen Sie Ihre Threads?
  • Ein typisches Skalierungsproblem des Speichermanagers und der RTL-String-bezogenen Implementierung.

Über den letzten Punkt, bedenken Sie dies:

  • Der aktuelle Speichermanager (FastMM4) skaliert nicht gut auf Multicore-CPU; Probieren Sie es mit einem Pro-Thread-Speichermanager aus, wie z. B. unserem experimentellen SynScaleMM - Hinweis z. dass das Free Pascal Compiler-Team kürzlich eine neue Skalierungs-MM geschrieben hat, um ein solches Problem zu vermeiden;
  • Versuchen Sie, die String-Prozessimplementierung zu ändern, um Speicherzuweisungen zu vermeiden (statische Puffer zu verwenden) und String-Reference-Counting (jeder String-Reference-Count-Zugriff erzeugt %code% , die auf Multi-Code-CPU nicht so gut skaliert thread char-level process, wobei zB %code% für statische Puffer anstelle von %code% ) verwendet wird.

Ich bin sicher, dass Sie ohne %code% -Operationen feststellen werden, dass alle Threads äquivalent sind.

Kurz gesagt: weder die aktuelle Delphi MM, noch die aktuelle String-Implementierung skaliert gut auf Multicore-CPU. Sie haben gerade ein bekanntes Problem der aktuellen RTL herausgefunden. Lesen Sie diese SO-Frage .

    
___ answer6742631 ___

Wenn alle Threads die gleiche Priorität haben, wie sie es normalerweise tun, kann es aus folgenden Gründen keinen Unterschied geben. Wenn Sie einen Unterschied feststellen, überprüfen Sie den Code erneut (stellen Sie sicher, dass Sie dasselbe in VCL- und Hintergrundthreads ausführen), und stellen Sie sicher, dass Sie die Zeit richtig einstellen:

  • Der Compiler generiert genau den gleichen Code, es ist egal, ob der Code im Hauptthread oder in einem Hintergrundthread ausgeführt wird. Tatsächlich können Sie den gesamten Code in eine Prozedur einfügen und diese sowohl vom %code% des Worker-Threads als auch vom VCL-Hauptthread aufrufen.

  • Für die CPU sind alle Kerne und alle Threads gleich. Es sei denn, es handelt sich tatsächlich um eine Hyperthreading-CPU, bei der nicht alle Kerne real sind, aber dann die nächste Kugel.

  • Auch wenn nicht alle CPU - Kerne gleich sind, wird Ihr Thread sehr unwahrscheinlich auf demselben Kern laufen , das Betriebssystem kann sie beliebig verschieben (und plant tatsächlich Ihren Thread zu verschiedenen Zeiten auf verschiedenen Kernen laufen).

  • Der Messaging-Overhead ist für den VCL-Hauptthread nicht von Bedeutung, denn wenn Sie %code% nicht manuell aufrufen, wird der Message-Pump einfach gestoppt, während Ihre Prozedur funktioniert. Die Nachrichtenpumpe ist passiv, Ihr Thread muss Nachrichten von der Warteschlange anfordern, aber da der Thread beschäftigt ist, Ihre Arbeit zu erledigen, fordert er keine Nachrichten an, also keinen Overhead.

Es gibt nur eine Stelle, an der Threads nicht gleich sind, und dies kann die wahrgenommene Ausführungsgeschwindigkeit ändern: Es ist das Betriebssystem, das Threads für Ausführungseinheiten (Kerne) plant und für die Betriebssystem-Threads unterschiedliche Prioritäten hat. Sie können dem Betriebssystem mitteilen, dass ein bestimmter Thread mithilfe des % anders behandelt werden muss. co_de% API (die von der %code% -Eigenschaft verwendet wird).

    
___ tag123multithreading ___ Multi-Threading ist die Fähigkeit eines Computers oder eines Programms, Arbeit gleichzeitig oder asynchron auszuführen, indem mehrere gleichzeitige Ausführungsströme (im Allgemeinen als Threads bezeichnet) verwendet werden. ___ tag123performance ___ Für Fragen zur Messung oder Verbesserung der Code- und Anwendungseffizienz. ___ answer6742845 ___

Fragen Sie, ob ein Hintergrund-Thread schneller wäre? Wenn Ihr Hintergrundthread den gleichen Code wie der Hauptthread ausführen würde und nichts anderes im Hauptthread stattfindet, können Sie nichts mit einem Hintergrundthread gewinnen. Threads sollten verwendet werden, um Verarbeitungslasten zu teilen und zu verteilen, die andernfalls miteinander konkurrieren und / oder sich gegenseitig blockieren würden, wenn sie im Hauptthread ausgeführt werden. Da Sie mit einem Fall zu tun haben, in dem Ihr Hauptthread ansonsten inaktiv ist, hilft das einfache Erstellen eines Threads zum Ausführen von langsamem Code nicht.

Threads sind keine Zauberei, sie können keinen langsamen Code beschleunigen oder Verarbeitungsengpässe in einem bestimmten Segment eliminieren, die nicht im Zusammenhang mit Konflikten im Hauptthread stehen. Stellen Sie sicher, dass Ihr Code nicht etwas tut, von dem Sie nichts wissen und dass Ihre Timing-Methode korrekt ist.

Meine erste Vermutung wäre, dass Ihre Interaktion mit der Steckdose Ihr Timing auf eine Weise beeinflusst, die Sie noch nicht entdeckt haben ... (Ich weiß, Sie sagten, Sie sind sicher, dass das nicht involviert ist - aber vielleicht nochmal nachschauen ...)

    
___ tag123delphi ___ Delphi ist eine Sprache für die schnelle Entwicklung von nativen Windows-, macOS-, Linux-, iOS- und Android-Anwendungen mithilfe von Object Pascal. Der Name bezieht sich sowohl auf die Delphi-Sprache als auch auf deren Bibliotheken, Compiler und IDE, mit denen Delphi-Projekte bearbeitet und debuggt werden können. ___ answer6747379 ___

Die Leistung kann nicht statisch bewertet werden. Dazu müssen Sie AQTime oder einen anderen Leistungsprofiler für Delphi abrufen. Ich benutze AQtime, und ich liebe es, aber ich bin mir bewusst, dass es teuer ist.

Ihr Code wird nicht magisch schneller, nur weil Sie ihn in einen Hintergrund-Thread verschoben haben. Die All-inclusive-Zeit, bis Sie Ergebnisse in Ihrer Benutzeroberfläche sehen, kann etwas langsamer werden, wenn Sie viele Daten aus dem Hintergrundthread mithilfe einiger Synchronisierungsmechanismen an den Vordergrundthread senden müssen.

Wenn Sie jedoch Teile Ihres Algorithmus parallel ausführen könnten, das heißt, Ihre Arbeit teilen, so dass Sie zwei oder mehr Worker-Threads haben, die Ihre Daten verarbeiten, und Sie einen Quad-Core-Prozessor haben, dann ist Ihre Gesamtzeit ein fester Vorgang Belastung der Arbeit, könnte abnehmen. Das bedeutet nicht, dass der Code schneller ausgeführt werden würde, aber abhängig von vielen Faktoren können Sie einen leichten Vorteil von Multithreading bis zur Anzahl der Kerne in Ihrem Computer erzielen. Es wird niemals ein 2x Performance-Boost sein, zwei Threads anstelle von einem zu verwenden, aber in Ihren mehr als ein Threads umfassenden parallelen Lösungen können Sie eine um 20-40% bessere Performance erzielen, je nachdem, wie skalierbar Ihr Heap ist unter Multithreading-Lasten und wie IO / Speicher / Cache Ihre Arbeitslast gebunden ist.

Was das Erhöhen der Threadprioritäten anbelangt, so wird im Allgemeinen alles, was Sie dort tun, das empfindliche Gleichgewicht der Leistung Ihres Windows-Systems stören. Durch die Erhöhung der Prioritäten erreichen Sie (manchmal) eine nominale, aber nicht wiederholbare und nicht garantierbare Leistungssteigerung. Abhängig von den anderen Dingen, die Sie in Ihrem Code und Ihren Datenquellen tun, kann das Spielen mit Prioritäten von Threads zu subtilen Problemen führen. Weitere Informationen finden Sie unter Restaurants Philosophen .

Die beste Möglichkeit, die Geschwindigkeit von String-Operationen zu optimieren, besteht darin, sie zuerst zu testen und genau herauszufinden, wo sie die meiste Zeit verwendet. Ist es Heap-Operationen? Speicher Kopieren und Verschieben von Vorgängen? Ohne einen Profiler, selbst mit Ratschlägen anderer Leute, werden Sie immer noch eine Kardinalsünde des Programmierens begehen; vorzeitige Optimierung. Ergebnisorientiert sein. Sei wissenschaftlich orientiert. Messen. Verstehen. Entscheide dich dann.

Nachdem ich das gesagt habe, habe ich eine Menge schrecklichen Code in meiner Zeit gesehen, und es gibt eine tolle Sache, die Leute machen, die ihre Thread-App-Performance total umbringt; Verwenden Sie TThread.Synchronize zu viel.

Hier ist ein pathologischer (Extrem-) Fall, der leider ziemlich häufig in freier Wildbahn vorkommt:

%Vor%

Das Problem hierbei ist, dass 100% der Arbeit wirklich im Vordergrund ausgeführt wird, abgesehen von der Prüfung "wenn beendet", die im Thread-Kontext ausgeführt wird. Um den obigen Code noch schlimmer zu machen, fügen Sie einen nicht unterbrechbaren Schlaf hinzu.

Verwenden Sie für schnellen Hintergrund-Thread-Code nur sehr sparsam oder gar nicht und stellen Sie sicher, dass der aufgerufene Code einfach ist und schnell ausgeführt wird, oder besser noch TThread.Queue oder PostMessage, wenn Sie wirklich mit der Haupt-Thread-Aktivität leben könnten .

    
___ tag123vcl ___ Dieses Tag wird für Fragen zur Visual Component Library verwendet, die in Borland / CodeGear / Embarcadero Delphi- und C ++ Builder-Produkten verwendet wird. Verwechseln Sie das nicht mit der Varnish Configuration Language (VCL). Verwenden Sie den "varnish-vcl" -Tag für Fragen zu diesem Produkt. ___ tag123timing ___ Timing ist ein numerisches Maß für die Dauer der Ausführung eines Befehls oder einer Anweisungsfolge ___ qstntxt ___

Wenn jemand viel Erfahrung mit Timing-Code hatte, der auf dem VCL-Hauptthread läuft, würde ich gerne eine Meinung dazu bekommen. Ich habe etwas Code, der in meiner Delphi 6-Anwendung auf dem Hauptthread einige schwere String-Verarbeitung ausführt. Jedes Mal, wenn ich eine Operation starte, schwebt die Zeit für jede Operation um 50 ms an einem einzelnen Thread auf meinem i5 Quad Core. Was mich wirklich misstrauisch macht ist, dass der selbe Code, der auf einem alten Pentium 4 läuft, dieselbe Zeit für die Operation anzeigt, wenn ich normalerweise sehe, dass Code auf dem Pentium 4 etwa 4 mal langsamer läuft als der Quad Core. Ich beginne mich zu fragen, ob der Code wesentlich weniger Zeit als 50 ms verbrauchen würde, aber dass etwas an dem Haupt-VCL-Thread, vielleicht Windows-Nachrichtenbehandlung oder Windows API-Aufrufe, ein künstliches "Floor" für den Vorgang erzeugt. Beachten Sie, dass eine Operation durch eine eingehende Anfrage an einem Socket ausgelöst wird, wenn dies von Bedeutung ist, die Zeitmessung jedoch erst stattfindet, wenn die Daten vollständig empfangen wurden.

Bevor ich den gesamten Code zum Testen auf einen Hintergrund-Thread verschiebe, frage ich mich, ob jemand in diesem Bereich allgemeines Wissen hat? Was waren Ihre Erfahrungen mit Code, der auf dem Haupt-VCL-Thread ausgeführt wird? Beachten Sie, dass die Timing-Messungen durchgeführt werden, wenn während der Tests absolut keine vom Benutzer ausgelöste Aktivität stattfindet.

Ich frage mich auch, ob es etwas bringen würde, die Priorität des Threads auf knapp unter Echtzeit zu erhöhen. Ich habe noch nie eine Verbesserung meiner Laufzeiten beim Experimentieren mit diesen Flags gesehen.

- roschler

    
___
Nat 19.07.2011 05:20
quelle
3

Die Leistung kann nicht statisch bewertet werden. Dazu müssen Sie AQTime oder einen anderen Leistungsprofiler für Delphi abrufen. Ich benutze AQtime, und ich liebe es, aber ich bin mir bewusst, dass es teuer ist.

Ihr Code wird nicht magisch schneller, nur weil Sie ihn in einen Hintergrund-Thread verschoben haben. Die All-inclusive-Zeit, bis Sie Ergebnisse in Ihrer Benutzeroberfläche sehen, kann etwas langsamer werden, wenn Sie viele Daten aus dem Hintergrundthread mithilfe einiger Synchronisierungsmechanismen an den Vordergrundthread senden müssen.

Wenn Sie jedoch Teile Ihres Algorithmus parallel ausführen könnten, das heißt, Ihre Arbeit teilen, so dass Sie zwei oder mehr Worker-Threads haben, die Ihre Daten verarbeiten, und Sie einen Quad-Core-Prozessor haben, dann ist Ihre Gesamtzeit ein fester Vorgang Belastung der Arbeit, könnte abnehmen. Das bedeutet nicht, dass der Code schneller ausgeführt werden würde, aber abhängig von vielen Faktoren können Sie einen leichten Vorteil von Multithreading bis zur Anzahl der Kerne in Ihrem Computer erzielen. Es wird niemals ein 2x Performance-Boost sein, zwei Threads anstelle von einem zu verwenden, aber in Ihren mehr als ein Threads umfassenden parallelen Lösungen können Sie eine um 20-40% bessere Performance erzielen, je nachdem, wie skalierbar Ihr Heap ist unter Multithreading-Lasten und wie IO / Speicher / Cache Ihre Arbeitslast gebunden ist.

Was das Erhöhen der Threadprioritäten anbelangt, so wird im Allgemeinen alles, was Sie dort tun, das empfindliche Gleichgewicht der Leistung Ihres Windows-Systems stören. Durch die Erhöhung der Prioritäten erreichen Sie (manchmal) eine nominale, aber nicht wiederholbare und nicht garantierbare Leistungssteigerung. Abhängig von den anderen Dingen, die Sie in Ihrem Code und Ihren Datenquellen tun, kann das Spielen mit Prioritäten von Threads zu subtilen Problemen führen. Weitere Informationen finden Sie unter Restaurants Philosophen .

Die beste Möglichkeit, die Geschwindigkeit von String-Operationen zu optimieren, besteht darin, sie zuerst zu testen und genau herauszufinden, wo sie die meiste Zeit verwendet. Ist es Heap-Operationen? Speicher Kopieren und Verschieben von Vorgängen? Ohne einen Profiler, selbst mit Ratschlägen anderer Leute, werden Sie immer noch eine Kardinalsünde des Programmierens begehen; vorzeitige Optimierung. Ergebnisorientiert sein. Sei wissenschaftlich orientiert. Messen. Verstehen. Entscheide dich dann.

Nachdem ich das gesagt habe, habe ich eine Menge schrecklichen Code in meiner Zeit gesehen, und es gibt eine tolle Sache, die Leute machen, die ihre Thread-App-Performance total umbringt; Verwenden Sie TThread.Synchronize zu viel.

Hier ist ein pathologischer (Extrem-) Fall, der leider ziemlich häufig in freier Wildbahn vorkommt:

%Vor%

Das Problem hierbei ist, dass 100% der Arbeit wirklich im Vordergrund ausgeführt wird, abgesehen von der Prüfung "wenn beendet", die im Thread-Kontext ausgeführt wird. Um den obigen Code noch schlimmer zu machen, fügen Sie einen nicht unterbrechbaren Schlaf hinzu.

Verwenden Sie für schnellen Hintergrund-Thread-Code nur sehr sparsam oder gar nicht und stellen Sie sicher, dass der aufgerufene Code einfach ist und schnell ausgeführt wird, oder besser noch TThread.Queue oder PostMessage, wenn Sie wirklich mit der Haupt-Thread-Aktivität leben könnten .

    
Warren P 19.07.2011 12:47
quelle
1

Fragen Sie, ob ein Hintergrund-Thread schneller wäre? Wenn Ihr Hintergrundthread den gleichen Code wie der Hauptthread ausführen würde und nichts anderes im Hauptthread stattfindet, können Sie nichts mit einem Hintergrundthread gewinnen. Threads sollten verwendet werden, um Verarbeitungslasten zu teilen und zu verteilen, die andernfalls miteinander konkurrieren und / oder sich gegenseitig blockieren würden, wenn sie im Hauptthread ausgeführt werden. Da Sie mit einem Fall zu tun haben, in dem Ihr Hauptthread ansonsten inaktiv ist, hilft das einfache Erstellen eines Threads zum Ausführen von langsamem Code nicht.

Threads sind keine Zauberei, sie können keinen langsamen Code beschleunigen oder Verarbeitungsengpässe in einem bestimmten Segment eliminieren, die nicht im Zusammenhang mit Konflikten im Hauptthread stehen. Stellen Sie sicher, dass Ihr Code nicht etwas tut, von dem Sie nichts wissen und dass Ihre Timing-Methode korrekt ist.

Meine erste Vermutung wäre, dass Ihre Interaktion mit der Steckdose Ihr Timing auf eine Weise beeinflusst, die Sie noch nicht entdeckt haben ... (Ich weiß, Sie sagten, Sie sind sicher, dass das nicht involviert ist - aber vielleicht nochmal nachschauen ...)

    
Vector 19.07.2011 05:54
quelle