Autor Beitrag
GuaAck
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 376
Erhaltene Danke: 32

Windows 8.1
Delphi 10.4 Comm. Edition
BeitragVerfasst: Do 12.09.13 00:23 
Hallo,

ich stelle gerade erstmalig ein Programm auf mehrere Threads um, damit moderne Mehrkernprozessoren aus genutzt werden können.

Mein Programm besteht grob aus aus:

A. Datensätze (32 Bytes) gemäß Programmcode Schritt für Schritt berechnen (kein Datei-I/O, pure CPU-Arbeit)
B. Jeden neu berechneten Datensatz speziell analysieren und in einer Paintbox im Hauptformular als Punkt in einem x-y-Diagramm darstellen.
Auf einem Ein-Kern-Rechner brauchen A und B ingesamt etwa die gleiche Zeit.

Umsetzung auf zwei Threads:
A ist der Haupt-Thread, B ist ein von A erzeugter Thread. A schreibt mit Writefile jeden Datensatz in eine (anonyme) Pipe, B liest per Readfile und arbeitet.
Das lief, aber langsamer als wenn alles im Haupthread gemacht wird.

Also habe ich Versuche auf einer (übersichtlicheren) Ein-Kern-Maschine gemacht und ein für mich unverständliches Ergebnis gefunden. Ich habe erwartet, dass die Threadumschaltung Zeit kostet, und deshalb einen großen Pipebuffer als vorteilhaft gesehen. Aber das Gegenteil ist der Fall!?

Pipebuffer = 1000 Aufträge = 32 kB: 470 ms, 30-60 neue Aktivierungen des Threads B
Pipebuffer = 100 Aufträge = 3 kB: 170 ms, um 75 neue Aktivierungen des Threads B
Pipebuffer = 10 Aufträge = 320 B : 180 ms, um 602 neue Aktivierungen des Threads B
Pipebuffer = 2 Aufträge = 64 B : 210 ms, um 1800 neue Aktivierungen des Threads B

(ohne Aufteilung auf zwei Thraeds werden 100 ms Zeit gebraucht.)

Hat da jemand eine Erklärung? (Windows XP SP3).

Gruß GuaAck
Martok
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: Do 12.09.13 04:38 
Moin,

user profile iconGuaAck hat folgendes geschrieben Zum zitierten Posting springen:
moderne Mehrkernprozessoren
Das ist sowas wie ein weißer Schimmel, oder? ;-)

user profile iconGuaAck hat folgendes geschrieben Zum zitierten Posting springen:
Hat da jemand eine Erklärung? (Windows XP SP3).
Pipes in Windows sind schnarchlangsam. Das hat irgendwas mit dem Windows-eigenen Locking darauf zu tun, den Artikel dazu (irgendwas auf CodeProject...) suche ich seit Jahren. Das war eine Gegenüberstellung der verschiedenen Kommunikationswege (Pipes, Mailslots, Sockets, WM_COPYDATA, diverse SharedMemory Implementationen), die man in Windows so für IPC hat. Ich find ihn zwischen den ganzen SharedMem-Implementationen, die seit ich den das erste mal gesehen habe entstanden sind einfach nicht wieder...

Spricht irgendwas dagegen, die Daten selbst in einem Ringpuffer zu verwalten? Eine feste Satzgröße macht das ja gradezu einfach, da du nur ein Array und je einen Lese-/Schreibzeiger brauchst.

user profile iconGuaAck hat folgendes geschrieben Zum zitierten Posting springen:
(ohne Aufteilung auf zwei Thraeds werden 100 ms Zeit gebraucht.)
Lohnt sich das dann überhaupt? Im besten Fall kostet ein neuer Thread (Setup, mindestens ein Context-Switch) um die 20ms, da müsste die Ersparnis schon magisch sein um das wieder rauszuholen.

Grüße,
Martok

_________________
"The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."

Für diesen Beitrag haben gedankt: GuaAck
OlafSt
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 486
Erhaltene Danke: 99

Win7, Win81, Win10
Tokyo, VS2017
BeitragVerfasst: Do 12.09.13 12:52 
Wenn die Threads innerhalb eines Prozesses liegen, würde ich auf IPC komplett verzichten. Die ist ja dafür da, um mehrere Prozesse miteinander reden zu lassen.

Ich würde MainThread A einfach die Blöcke berechnen lassen und in eine TQueue schieben (Thread-Safety beachten !). Thread B fischt anschließend einen Block aus der Queue heraus und grübelt daran weiter. So können beide Threads unabhängig voneinander an den Daten herumrechnen und Thread B wartet sogar von ganz allein auf neue Daten, weil MeinThread A ja noch mehr am Hals hat (Message-Queue bearbeiten, Paintbox zeichnen etcpp).

Da Thread A und B jeweils gleiche Laufzeiten haben, lohnt sich mehr als ein B-Thread eh nicht - der A-Thread bekommt gar nicht schnell genug Daten bereitgestellt für mehr als einen B-Thread.

_________________
Lies, was da steht. Denk dann drüber nach. Dann erst fragen.

Für diesen Beitrag haben gedankt: GuaAck
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Do 12.09.13 17:53 
Wie sieht denn deine Synchronisation mit dem Formular zur Anzeige aus? Denn da liegt oft der Hund begraben.

Mit Pipes hatte ich was die Geschwindigkeit angeht bisher weniger Probleme, da deren Verwaltung in der Regel trotzdem um Größenordnungen unter der der Aufgaben lag. Es gibt aber auch schnellere Lösungen, klar.

Für diesen Beitrag haben gedankt: GuaAck
GuaAck Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 376
Erhaltene Danke: 32

Windows 8.1
Delphi 10.4 Comm. Edition
BeitragVerfasst: Do 12.09.13 22:22 
Hallo und Danke an Alle,

a) Danke für den "weißen Schimmel".... stimmt schon.

b) TQueue: TQueue.pop wartet bei mir nicht, sondern ergibt eine Fehlermeldung, wenn die Queue leer ist. (Ich habe Delphi 7.)

c) Ringpuffer war mein erster Ansatz. Der Puffer ging gut, aber das Problem, den Lese-Thread zu aktivieren, wenn es Arbeit gibt (ohne da bei jedem Schreiben zu machen) klappte nicht.

d) Synchronisation mit Formular: Synchronize usw. nutze ich nicht. Außerdem, damit kann ich mir das unerwartete Zeitverhalten nicht erklären.

Jaenicke: Genau Dein Argument veranlasste mich zu der Erwartung, dass eine "lange" Pipe, in der erst einmal vieles gespeichert wird bevor ein Threadwechsel gemacht wird, schneller sein müsste, als wenn man nur eine sehr kurze Pipe hätte. Aber genau das Gegeteil habe ich ja beobachtet.


Ich werde die Ringpufferlösung noch einmal aufgreifen.

Danke nochmals,
Gruß
GuaAck
OlafSt
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 486
Erhaltene Danke: 99

Win7, Win81, Win10
Tokyo, VS2017
BeitragVerfasst: Do 12.09.13 23:12 
Zum einen kann man die TQueue.POP-Exception abfangen und dazu benutzen, den Thread schlafen zu legen. Zum anderen kann man mit TQueue.Count nachschauen, ob was drin ist ;)

Noch besser wäre ein Event. Thread A stellt den Event auf Signalled, nachdem Daten in die Queue geschoben wurden. Thread B wartet auf Signalled und bearbeitet die Aufgaben so lange, bis TQueue.Count = 0 ist. Dann wartet er wieder auf Signalled.

_________________
Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Do 12.09.13 23:40 
Lässt sich daraus vielleicht ein Beispielprojekt machen, das einfach Dummydaten erzeugt (z.B. einfach nen Sleep statt echter Berechnung)? Dann könntest du da nur die relevanten Teile drin lassen ohne dein echtes Projekt posten zu müssen, und wir könnten dir konkret helfen...