Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Delphi Generelle Fragen zu TParallel.For (https://www.delphipraxis.net/191268-generelle-fragen-zu-tparallel.html)

a.def 27. Dez 2016 18:25

Generelle Fragen zu TParallel.For
 
Wie sicher mitbekommen bin ich vor Kurzem auf den TParallel-Zug mit aufgestiegen.
Bisher verläuft alles super. In Verbindung mit einer Thread-sicheren TStringList funktioniert bei mir nun alles einwandfrei und dennoch habe ich ein paar Fragen.

1. bringt eine TParallels.For-Schleife irgendwelche Vorteile gegenüber der normalen For-Schleife auf einem 1-Kern-System?

2. ich lasse einiges an kleinen Log-Daten (unter 100 KB) in einem TThread kopieren. Das geschieht dann über TParallels.For IM TThread.
- wenn ich die Daten mit einem TSHFileOpStruct-Konstrukt kopiere scheint es schneller zu sein als Windows.CopyFileEx (ohne CallBack-Funktion).
- könnte das an TParallel.For liegen oder ist das "manuelle" kopieren mit TSHFileOpStruct einfach besser (weil weniger Overhead) als CopyFileEx in einem Thread?

3. Ich habe keinen Unterschied bemerkt. Aber gibt es einen Unterschied zwischen TParallels.For und TParallels.&For?

4. Ich denke eher nicht, dass man die Threadanzahl von TParallels.For selbst bestimmen kann. Wenn doch, wie? Ich habe etwas von einem Threadpool gelesen, es aber nicht ganz verstanden.

Fritzew 27. Dez 2016 20:33

AW: Generelle Fragen zu TParallel.For
 
Hallo
Zitat:

1. bringt eine TParallels.For-Schleife irgendwelche Vorteile gegenüber der normalen For-Schleife auf einem 1-Kern-System?
Wie immer... kommt darauf an was in dem Thread gemacht wird. Wenn da nur gerechnet wird wohl eher nicht. Wird aber auf verschiedene!! Ressourcen zugegriffen könnte es schneller sein. Pauschal denke ich wohl nicht zu beantworten.



Zitat:

3. Ich habe keinen Unterschied bemerkt. Aber gibt es einen Unterschied zwischen TParallels.For und TParallels.&For?
Das ist das selbe. For ist ein Schlüsselwort das mit & verwendet werden kann.

Uwe Raabe 27. Dez 2016 21:16

AW: Generelle Fragen zu TParallel.For
 
Zitat:

Zitat von a.def (Beitrag 1357296)
1. bringt eine TParallels.For-Schleife irgendwelche Vorteile gegenüber der normalen For-Schleife auf einem 1-Kern-System?

Du blockierst damit zumindest nicht den Hauptthread.

Zitat:

Zitat von a.def (Beitrag 1357296)
4. Ich denke eher nicht, dass man die Threadanzahl von TParallels.For selbst bestimmen kann. Wenn doch, wie? Ich habe etwas von einem Threadpool gelesen, es aber nicht ganz verstanden.

Wenn du eine eigene ThreadPool-Instanz übergibst, kannst du darin vorher über SetMinWorkerThreads und SetMaxWorkerThreads schon etwas einstellen, allerdings in gewissen Grenzen.

jaenicke 27. Dez 2016 22:21

AW: Generelle Fragen zu TParallel.For
 
Zitat:

Zitat von a.def (Beitrag 1357296)
2. ich lasse einiges an kleinen Log-Daten (unter 100 KB) in einem TThread kopieren. Das geschieht dann über TParallels.For IM TThread.
- wenn ich die Daten mit einem TSHFileOpStruct-Konstrukt kopiere scheint es schneller zu sein als Windows.CopyFileEx (ohne CallBack-Funktion).
- könnte das an TParallel.For liegen oder ist das "manuelle" kopieren mit TSHFileOpStruct einfach besser (weil weniger Overhead) als CopyFileEx in einem Thread?

Wenn du nicht gerade eine SSD hast, auf der die Dateien liegen, bremst du dich mit Zugriffen mit mehreren Threads selbst aus, weil der Kopf der Festplatte ständig hin- und herspringen muss. Wird nur ein Vorgang gleichzeitig ausgeführt, geht das daher in aller Regel schneller.

Beschleunigen kannst du so etwas am ehesten indem du selbst die Dateien in den Arbeitsspeicher einliest und in einem Rutsch schreibst. Groß wird der Vorteil dadurch aber nicht unbedingt sein bei der Dateigröße.

a.def 27. Dez 2016 22:58

AW: Generelle Fragen zu TParallel.For
 
Kopiert werden 5000 x 56,1 KB (FileFiller) von HDD auf einen etwas älteren USB-Stick. Das alles in 4 bis 5 Minuten.
Die Schreibgeschwindigkeit meiner Demo liegt laut Berechnung bei etwa 0,9 bis 1,1 MB pro Sekunde (4 erzeugte Threads, jeder Thread mit TParallel.For, laut TaskManager 40+ Threads).
Die am Ende berechnete durchschnittliche Schreibgeschwindigkeit liegt bei etwa 850 KB pro Sekunde (15 Dateien pro Sekunde).

Windows benötigt dieselbe Zeit (mehrfach getestet, mit mehreren USB-Sticks, zwischenzeitlich formatiert usw.)

jaenicke 28. Dez 2016 03:04

AW: Generelle Fragen zu TParallel.For
 
Klar, wenn du nur lesend auf die Festplatte zugreifst und einen langsameren Flashspeicher zum Schreiben nutzt, hast du das Problem nicht.

Die maximale Anzahl der Operationen des Flashspeichers pro Sekunde ist aber gleich, ob mit einem oder mit mehreren Threads.

a.def 28. Dez 2016 09:25

AW: Generelle Fragen zu TParallel.For
 
Ich habe das gerade mal mit Quelle Festplatte D und Ziel Festplatte D gemacht.
Der Festplatten-Cache war denke ich leer, da der PC gerade erst gestartet wurde. Zumindest waren nicht bereits die Daten im Cache die ich kopiert habe.
Das hat 59 Sekunden gedauert. Gleicher Test wie oben: 5000 Dateien je 56,1 KB mit FileFiller erstellt.

a.def 30. Dez 2016 21:37

AW: Generelle Fragen zu TParallel.For
 
Eine Frage habe ich leider noch.
Ich versuche gerade möglichst viel auf TParallels.For umzustellen.

Ich habe eine for-Schleife welche eine TObjectList füllt.
Reicht es hier, wenn ich drumherum mit Queue(procedure begin end); arbeite?
Der Rest mit den Variablen ist alles kein Problem dank TInterlocked.

jaenicke 31. Dez 2016 12:57

AW: Generelle Fragen zu TParallel.For
 
TParallel.For mag zwar verlockend sein, aber wenn du zu viel Synchronisation, Queue, ... benötigst, ist es nicht unbedingt die beste Lösung. Oft ist eine manuelle Lösung mit Threads schneller.
Das kommt vor allem darauf an wie lange ein einzelner Durchlauf dauert.

Wenn du z.B. 10000 Schleifendurchläufe hast, die sequentiell 3 Sekunden dauern, macht es eher Sinn das ganze in z.B. 4-8 Threads mit je 1250-2500 Durchläufen aufzuteilen und die Ergebnisse nur noch am Ende zusammenzuführen. Denn dann sparst du dir viel Overhead, so dass das ganze am Ende oft schneller ist. Insbesondere weil die meisten Rechner auch nur 4-8 Kerne haben.

Zur Frage:
Ja, Queue reicht. Aber wenn du das zu oft benutzt, ist das wie gesagt ein ziemlicher Verwaltungsaufwand.

a.def 31. Dez 2016 17:46

AW: Generelle Fragen zu TParallel.For
 
Ich habe deinen Rat befolgt.

Ich verwende nun 2x TTask.Run();
In jedem dieser rufe ich eine einzige bestimmte Funktion auf, welche durch meine StringListe geht (einige Parameter, zudem Start und Ende der For-Schleife)
Folgendermaßen berechne ich, welcher der beiden Runs was zu bearbeiten hat

Delphi-Quellcode:
run1 := 0;
run2 := 0;

// Beispiel: sl.Count = 5001
for i := 0 to (sl.Count div 2) - 1 do // 0 bis 2500
 Inc(run1);

for i := run1 to sl.Count - 1 do // 2501 bis 5000
 Inc(run2);

TTask.Run(
 procedure
  begin
   TThread.Queue(nil,
    procedure
     begin
      dummyFunction(param1, param2, ..., 0, run1);
     end);
  end);

TTask.Run(
 procedure
  begin
   TThread.Queue(nil,
    procedure
     begin
      dummyFunction(param1, param2, ..., run2, sl.Count - 1);
     end);
  end);
Wenn der zweite Aufruf von dummyFunction fertig bevor der erste fertigist, wartet der zweite Aufruf (Boolean Variable), bis der erste komplett.. komplett.. fertig ist.
Am ende des zweiten Aufrufs wird in dummyFunction() alles zusammengeführt (nur ein paar Zählervariablen).

Vorher brauchte das mit meinen Testdaten 2,1 bis 2,2 Sekunden im Schnitt. Nun bin ich bei 1,4 bis 1,5 im Schnitt.
Ich denke ich werde noch etwas einbauen, dass das TTask.Run nur bei Mehrkernsystemen verwendet wird.

Schöner wär's natürlich, wenn das alles super dynamisch wäre, ohne feste Variablen wie meine booleschen aktuell. Aber das kommt später wenn ich den Durchblick habe.

a.def 31. Dez 2016 20:50

AW: Generelle Fragen zu TParallel.For
 
Irgendetwas stimmt da mit meiner Zahlenkunst aber noch nicht.
Wenn ich eine StringListe mit 10 Einträgen habe:

Delphi-Quellcode:
{*
 => sl.Count = 10 Einträge
 
1:
- 0   bis (sl.Count-1) div 2 == 0 bis 4 (5 Strings)

2:
- 4+1 bis (sl.Count-1)      == 5 bis 9 (5 Strings)

*}
Nur wie müssen dann die Schleifen aussehen, die die Variablen hochzählen? :roll:
Delphi-Quellcode:
for i := 0 to ((sl.Count) div 2) - 1 do
 Inc(run1);

for i := run1 to sl.Count - 1 do
 Inc(run2);

ShowMessage('Von ' + IntToStr(0) + ' bis ' + IntToStr(run1) + sLineBreak + 'Von ' + IntToStr(run2) + ' bis ' + IntToStr(sl.Count - 1));
Das hier ergibt "0 bis 5" und "5 bis 9".
Bei ungeraden Zahlen (11 Einträge) ist es korrekt.

nahpets 31. Dez 2016 21:15

AW: Generelle Fragen zu TParallel.For
 
In der ersten Schleife zählst Du von 0 bis zum berechneten Wert.

Mit diesem Wert beginnst Du die zweite Schleife. Die müsste aber beim ersten Wert hinter dem Ende der ersten Schleife beginnen.

Müsste es nicht eher so aussehen?
Delphi-Quellcode:
program Test;
var
        i     : Integer;
        run1   : Integer;
        run2   : Integer;
        sl    : TStringList;
begin
  sl := TStringList.Create;
  for i := 0 to 100 do sl.Add('');
  run1 := 0;
  run2 := 0;

  for i := 0 to (sl.Count div 2) do Inc(run1);

  for i := (run1 + 1) to sl.Count - 1 do Inc(run2);

  ShowMessage('Von ' + IntToStr(0) + ' bis ' + IntToStr(run1) + #13#10 + 'Von ' + IntToStr(run1 + 1) + ' bis ' + IntToStr(sl.Count - 1));
  sl.Free;
end.

a.def 31. Dez 2016 21:22

AW: Generelle Fragen zu TParallel.For
 
Dein Ansatz funktioniert.
Folgender funktioniert auch
Beides scheint aber nicht perfekt zu funktionieren.

Delphi-Quellcode:
iTmp := -1;
iTmpEx := 0;

slMidIndex := slSource.Count div 2; // center of list's items
slHighIndex := slSource.Count - 1; // end of list

for i := slMidIndex to slHighIndex do
 Inc(iTmp);

for i := slHighIndex downto slMidIndex do
 Inc(iTmpEx);

ShowMessage('Von ' + IntToStr(0) + ' bis ' + IntToStr(iTmp) + sLineBreak + 'Von ' + IntToStr(iTmp + 1) + ' bis ' + IntToStr(slHighIndex));
 
// 9 Einträge
// Von 0 bis 4
// Von 5 bis 8

// 10 Einträge
// Von 0 bis 4
// Von 5 bis 9

// 11 Einträge
// Von 0 bis 5
// Von 6 bis 10

// http://stackoverflow.com/questions/9211772/how-to-split-one-stringlist-into-two-string-lists-in-delphi

nahpets 31. Dez 2016 21:43

AW: Generelle Fragen zu TParallel.For
 
Nein, der Startwert der zweiten Schleife muss um eins größer sein, als der Endwert der ersten Schleife, andernfalls sind der letzte Wert der ersten For-Schleife und der Startwert der zweiten For-Schleife identisch und es wird ein Verarbeitungsschritt zweimal durchgeführt.

Bei Deiner derzeitigen Variante wird zweimal die obere Hälfte der Liste verarbeitet.

Einmal von der Mitte bis zum Ende und einmal vom Ende bis zur Mitte. Die Ausgabe suggeriert jedoch, es würden beide Hälften verarbeitet.

a.def 31. Dez 2016 22:20

AW: Generelle Fragen zu TParallel.For
 
Ein paar Tests von mir (10 Dateien eines Verzeichnisses kopieren) sind bisher alle ohne Fehler.

Uwe Raabe 1. Jan 2017 12:15

AW: Generelle Fragen zu TParallel.For
 
Zitat:

Zitat von a.def (Beitrag 1357659)
Ein paar Tests von mir (10 Dateien eines Verzeichnisses kopieren) sind bisher alle ohne Fehler.

Würdest du es bemerken, wenn eine von den 10 Dateien doppelt kopiert würde?

a.def 1. Jan 2017 12:35

AW: Generelle Fragen zu TParallel.For
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1357668)
Zitat:

Zitat von a.def (Beitrag 1357659)
Ein paar Tests von mir (10 Dateien eines Verzeichnisses kopieren) sind bisher alle ohne Fehler.

Würdest du es bemerken, wenn eine von den 10 Dateien doppelt kopiert würde?

Ja. Genau für diesen Fall habe ich in meiner Demo eine ListBox wo eine Art "Journal" geführt wird.
Zusätzlich wird mir vorher die fertige Liste angezeigt.

Uwe Raabe 1. Jan 2017 14:09

AW: Generelle Fragen zu TParallel.For
 
Diese beiden Schleifen

Delphi-Quellcode:
for i := slMidIndex to slHighIndex do
 Inc(iTmp);

for i := slHighIndex downto slMidIndex do
 Inc(iTmpEx);
durchlaufen beide denselben (oberen) Index-Bereich, nur anders herum. Der untere Teil von 0 bis slMidIndex - 1 wird gar nicht abgedeckt. Andererseits zählst du in diesem Beispiel auch nur zwei Zähler hoch und greifst nicht auf die Einträge der Liste zu. Am Ende interpretierst du dann aber die Endwerte der Zähler als etwas, das sie gar nicht sind.

Delphi-Quellcode:
ShowMessage('Von ' + IntToStr(0) + ' bis ' + IntToStr(iTmp) + sLineBreak + 'Von ' + IntToStr(iTmp + 1) + ' bis ' + IntToStr(slHighIndex));

Dabei stellt iTmp aber lediglich die Anzahl Durchläufe der ersten Schleife dar. Das deckt sich aber nicht mit der Meldung in ShowMessage.

Der Code, den du hier zeigst, ist ganz offensichtlich nicht der, den du ausführst.

Ehrlich gesagt, verstehe ich überhaupt nicht, wo das Problem ist. Das Splitten einer Liste in zwei nahezu gleich große Teile ist doch wirklich trivial.

a.def 1. Jan 2017 15:02

AW: Generelle Fragen zu TParallel.For
 
Zitat:

Der Code, den du hier zeigst, ist ganz offensichtlich nicht der, den du ausführst.
Ehrlich gesagt, verstehe ich überhaupt nicht, wo das Problem ist. Das Splitten einer Liste in zwei nahezu gleich große Teile ist doch wirklich trivial.
Doch. So wie er da steht verwende ich ihn aktuell leider.

Auch wenn es sehr trivial zu sein scheint, bekomme ich es anders nicht hin :(

nahpets' Lösung scheint aber doch richtig zu sein oder?
Zitat:

Zitat von nahpets (Beitrag 1357656)
In der ersten Schleife zählst Du von 0 bis zum berechneten Wert.

Mit diesem Wert beginnst Du die zweite Schleife. Die müsste aber beim ersten Wert hinter dem Ende der ersten Schleife beginnen.

Müsste es nicht eher so aussehen?
Delphi-Quellcode:
program Test;
var
        i     : Integer;
        run1   : Integer;
        run2   : Integer;
        sl    : TStringList;
begin
  sl := TStringList.Create;
  for i := 0 to 100 do sl.Add('');
  run1 := 0;
  run2 := 0;

  for i := 0 to (sl.Count div 2)-1 do Inc(run1); // hier habe ich -1 hinzugefügt

  for i := (run1 + 1) to sl.Count - 1 do Inc(run2);

  ShowMessage('Von ' + IntToStr(0) + ' bis ' + IntToStr(run1) + #13#10 + 'Von ' + IntToStr(run1 + 1) + ' bis ' + IntToStr(sl.Count - 1));
  sl.Free;
end.


Uwe Raabe 1. Jan 2017 16:54

AW: Generelle Fragen zu TParallel.For
 
Was soll eigentlich das Hochzählen irgendwelcher Variablen bezwecken? Geht es nicht einzig darum, eine Liste in zwei separaten Schleifen zu durchlaufen, wobei
  1. die Anzahl Durchläufe möglichst gleich ist
  2. keine Einträge doppelt verarbeitet werden sollen

Dazu errechnet man die ungefähre Mitte (bei ungerader Anzahl geht das nicht genau auf):
Delphi-Quellcode:
(sl.Count div 2)
Dann lässt man die eine Schleife von
Delphi-Quellcode:
0
bis
Delphi-Quellcode:
(sl.Count div 2) - 1
laufen und die zweite von
Delphi-Quellcode:
(sl.Count div 2)
bis
Delphi-Quellcode:
slCount - 1
.

Mir ist nicht ganz klar, warum in deinem Beispiel die zweite Schleife bei
Delphi-Quellcode:
run1 + 1
loslegt. Das ergibt doch gar keinen Sinn. Was hat der Zähler
Delphi-Quellcode:
run1
mit den Schleifengrenzen bzw. der Aufteilung der Schleifen zu tun?


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:55 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz