Entwickler-Ecke

Windows API - Nur 1 CPU-Kern belastet trotz Threads


Bergmann89 - Mo 28.02.11 05:42
Titel: Nur 1 CPU-Kern belastet trotz Threads
Hey,

ich hab heute ein Teil meiner Anwendung erfolgreich in Threads ausgelagert. Zur Zeit arbeitet alles fehlerfrei und liefert die gewünschten Ergebnisse. Jedoch wird immer noch nur ein Kern der CPU belastet. Ich dachte immer die Verteilung der Threads auf die Kerne übernimmt Windows, oder muss ich da doch noch was machen?
Oder liegt es vlt daran, das ich die Arbeits-Threads in einem seperaten Thread erzeuge? Muss ein Thread immer im Kontext des MainThreads erstellt werden? Eh ich das Programm jetzt wieder total zerpflücke und am Ende nichts gekonnt hab wollte ich vorher mal fragen.

€: Hab mal zum Test ein Synchronize um die Methode gepackt, die die Threads erstellt, sodass die Threads im Kontext des MainThreads erstellt werden. Hat sich aber nix geändert, also muss es doch einen anderen Grund haben.

MfG Bergmann.


Delete - Mo 28.02.11 05:48

Also das Verteilen auf die CPU Kerne musst du schon selber regeln.


jaenicke - Mo 28.02.11 06:36

Seit wann das denn? :shock:

Also beim mir übernimmt das Windows automatisch wie es sein sollte. Ich habe das jetzt extra nochmal getestet mit nem simplen while true do als Thread. Bei jedem neu erzeugten Thread wuchs die CPU-Auslastung um ca. 16% bis bei 5 Threads eine Auslastung von knapp 90% da ist. Genauer gesagt langweilt sich nur noch ein Kern zur Hälfte während die anderen 5 (mit jeweils einem Thread) komplett ausgelastet sind.

Selbst verteilen muss man nur, wenn man will, dass bestimmte Threads auf bestimmten Kernen bleiben, aber was die lastverteilung angeht macht das Windows sonst auch selbst. Das würde das Thread- und Multicore-Prinzip sonst ja auch ad absurdum führen.

Ich vermute im konkreten Fall, dass die Threads verschiedene Lasten erzeugen. Der eine belastet den Kern vielleicht wirklich komplett, die anderen den anderen Kern nicht. ;-)


Martok - Mo 28.02.11 06:36

user profile iconLuckie hat folgendes geschrieben Zum zitierten Posting springen:
Also das Verteilen auf die CPU Kerne musst du schon selber regeln.

Ich würde das anders ausdrücken: Du musst den Algorithmus so umsetzen, dass überhaupt mehr als ein Thread gleichzeitig Arbeit (und Ressourcen) hat.

Wenn du 42 Threads hast und alle drauf warten dass einer fertig wird, bringt das genau gar nix.


jaenicke - Mo 28.02.11 06:38

Ok, so interpretiert ist die Aussage natürlich richtig. ;-)


Bergmann89 - Mo 28.02.11 06:48

Hey,

ich erzeuge 6 Threads (für jeden Kern einen). Die Threas bekommen jeweils den gleichen Satz Daten (teilweiße der gleiche Speicher, aber davon wird nur gelesen). Aus diesen Daten sollen dann neue Datensätze erzeugt werden und in einer Liste gespeichert werden. Den Zugriff auf die Liste hab ich mit nem Semaphor geschützt. Ich kann auch vorher berechnen wieviel neue Datensätze ich erhalte. Nehmen wir mal als Bsp. 6000 Datensätze. Also soll jeder Thread 1000 Datensätze berechnen, der 1. Startet bei 0, der 2. bei 1000, usw. Also hat jeder Thread genau gleich viel zu tun. Zuerst hab ich gedacht, das es an dem synchronisierten Zugriff auf die Liste liegt, also hab ich das speichern der Datensätze auskommentiert. Die Daten werden jetzt nur berechnet und dann wieder verworfen. Dabei arbeitet jeder Thread mit lokalen Variablen (außer den oben genannten, die nur gelesen werden), also hab ich auch nix, was synchronisiert werden muss. Und trotzdem läuft die Anwendung nur mit 17% CPU-Last und das entspricht der Volllast eines Kerns (bei 6 Kernen).

MfG Bergmann.


jaenicke - Mo 28.02.11 08:13

Naja, wie gesagt, schreib die eine Zeile in einen Thread und schau was passiert. ;-)

Das muss also an deiner Threadarchitektur liegen. Da wir deinen Quelltext nicht kennen, können wir aber auch nicht viel dazu sagen. :nixweiss:

Kannst du vielleicht ein kleines Demoprogramm erstellen mit dem Threadcode? (Oder wenn du das nicht hier posten möchtest, kannst du es mir wenn du willst auch per PN schicken, dann schaue ich es mir heute Abend an.)


Tilo - Mo 28.02.11 13:31

user profile iconBergmann89 hat folgendes geschrieben Zum zitierten Posting springen:
Die Threas bekommen jeweils den gleichen Satz Daten (teilweiße der gleiche Speicher, aber davon wird nur gelesen). Aus diesen Daten sollen dann neue Datensätze erzeugt werden und in einer Liste gespeichert werden. Den Zugriff auf die Liste hab ich mit nem Semaphor geschützt.


Was wird den alles durch die Semaphore gesperrt?
Wie arbeiten die Threads?
Wird z.B. jeder Datensatz nach dem erzeugen sofort in die Liste gespeichert (pro Thread und Datensatz ein Zugriff auf die Liste)arbeiten die Threads nicht paralell das sie immer darauf warten das die Liste frei wird.
Sollten die Threads erst die Daten erzeugen und dann in die Liste schreiben (d.H. pro thread nur ein einmaliger Zugriff auf die Liste) dürftest du am Anfang eine hohe Auslastung und dann eine kleine Auslatung haben.


Sinspin - Mo 28.02.11 14:58

Arbeitest Du in den Threads vieleicht mit dynamischen Arrays oder langen Strings deren Länge Du ständig änderst?
Bist Du Dir sicher das alle Threads "auf einmal" gestartet werden und das Programm dann nur wartet bis alle fertig sind?


Bergmann89 - Mo 28.02.11 15:05

Hey,

@Tilo: die Threads schreiben die Daten sofort nach der Berechnung in die Liste. Also hab ich pro Datensatz einen Zugriff auf die Liste. Ein Thread braucht ca. 1-10sec (je nach Daten) um einen neuen Datensatz zu erzeugen. Im Schnitt sind es ca. 3sec. Ich denke das 3sec lange genug sind, das sich die Threads nicht jedesmal gegenseitig behindern. Das schreiben in die Liste geht auch schnell. is nur ein fList.Add(p); un bisl Synchronisation rund rum. Aber um ganz sicher zu gehen hab ich das Speichern in der Liste auch mal auskommentiert (sodass jeder Thread für sich selbst arbeitet) und trotzdem hab ich nur 17% CPU-Last.

@Sinspin: dyn. Arrays und Strings eher weniger. Hab ales geschickt in Klassen und Pointer verpackt^^ Die Threads werden in einer Schleife erzeugt und gestartet. Also kann man schon von "gleichzeitig" reden. Die Anwendung wartet dann auf das alle 6 Threads fertig sind.

Ich hab noch 2-3 Sachen die ich noch ausprobieren kann, das mach ich jetzt mal schnell und dann meld ich mich nochma.

MfG Bergmann.


Bergmann89 - Do 31.03.11 22:09

Hey,

ich dachte eigentlich das ich das Problem gelöst habe, bis ich heute festgestellt hab, das ich beim Umbau vergessen hab eine Variable zu initialisieren und jetzt hab ich wieder das gleiche Problem: 6 Threads auf einem CPU-Kern -.-
Ich konnt das ganze jetzt sehr stark einschränken, aber jetz bin ich ratlos. Es liegt an folgender Methode:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//fügt einen Tip in den Suchbaum ein
//@Tip: Tipp der hinzugefügt werden soll;
procedure TTipTree.Add(const Tip: TTip);
var
  TmpItem: TTipTreeItem;
  i: Integer;
  b: Byte;
begin
  TmpItem := fRoot;
  for i := 0 to fGameCount-1 do begin 
    b := GetTipChar(Tip, i);
    fTipCheck[b] := fTipCheck[b] and not(1 shl i);
    if not Assigned(TmpItem[b]) then begin 
      TmpItem[b] := TTipTreeItem.Create; //HIER ist der Fehler
      TmpItem[b].fValue := b; 
    end;
    TmpItem := TmpItem[b]; 
  end;
  inc(fCount);
end;

Wenn ich die gekennzeichnete Zeile ausführe, dann kommt es zu dem beschriebenen Fehler. An den 2 Zeilen weiter unten kann es auch nicht liegen, weil ich die zum Test auch schon auskommentiert hab. Hier mal noch der Code von TTipTreeItem:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
type
  TTipTreeItem = class(TObject)
  private
    fValue: Byte;
    fChildren: array[0..2of TTipTreeItem;

    procedure SetChild(Index: Integer; Value: TTipTreeItem);
    function GetChild(Index: Integer): TTipTreeItem;
  public
    property Value   : Byte         read fValue   write fvalue;
    property Children[Index: Integer]: TTipTreeItem read GetChild write SetChild; default;

    procedure Clear;
    constructor Create;
    destructor Destroy; override;
  end;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Set-Methode der Children-Eigenschaft
procedure TTipTreeItem.SetChild(Index: Integer; Value: TTipTreeItem);
begin
  fChildren[Index] := Value;
end;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Get-Methode der Children-Eigenschaft
function TTipTreeItem.GetChild(Index: Integer): TTipTreeItem;
begin
  result := fChildren[Index];
end;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//gibt alle Kinder das Objekts frei
procedure TTipTreeItem.Clear;
var
  i: Integer;
begin
  for i := 0 to 2 do
    FreeAndNil(fChildren[i]);
end;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//erstellt das Objekt
constructor TTipTreeItem.Create;
var
  i: Integer;
begin
  inherited Create;
  for i := 0 to 2 do
    fChildren[i] := nil;
end;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//gibt das Objekt frei
destructor TTipTreeItem.Destroy;
begin
  Clear;
  inherited Destroy;
end;

Von den TTipTree-Objekten hat auch jeder Thread sein eigenes, also können die sich da auch nicht in die Quere kommen. Ich seh da absolut kein Fehler drin. Kann sich das jmd von euch erklären?

MfG Bergmann.


Delphi-Laie - Fr 01.04.11 00:06

Eine substantielle Hilfe kann ich Dir leider nicht bieten, aber vielleicht hilft es Dir weiter, mal zu überprüfen, ob man Thread <-> Prozessor "von außen" bzw. "gewaltsam" zuordnen kann.

Da der Prozeßexplorer der Sysinternals das nur bei Prozessen, nicht jedoch bei Threads beherrscht, machte ich mich an einen Versuch, denn die Windows-API stellt die nötigen Befehle durchaus bereit.

Deshalb mein Vorschlag: Kopiere Dir von http://www.delphiforum.de/viewtopic.php?t=92373&highlight=proze%DFbetrachter die Delphi- und/oder die Lazarusversionen. Nach dem Doppelklick auf die Threadanzahl des betreffenden Prozesses öffnet sich das zugehörige Threadfenster (des jeweiligen Prozesses). Mit Rechtsklick auf die einzelnen Threads sollten manuelle Prozessorzuordnungen möglich sein/werden.


Bergmann89 - Fr 01.04.11 02:56

Hey,

hab das grad mal ausprobiert. Wenn ich das Threadfenster auf habe und da auf einen Thread rechtsklicke passiert nichts. Das Programm hat mir aber auch gesagt das es keine Adminrechte hat, obwohl ich es als Admin ausgeführt habe. Habs mit allen 4 Versionen probiert. Die Lazarusversionen hab ich dann auch nochma getestet, da kommt aber immer ne AccesViolation sobald ich irgendwo drauf klick...

MfG Bergmann


Delphi-Laie - Fr 01.04.11 19:34

Deine Mißerfolge sind bedauerlich und - auch wenn das die Standardantwort in solchen Fällen ist, so ist sie dennoch wahr - für mich leider nicht nachvollziehbar.

Bei mir tun die Compilate auf mehreren Windows-Versionen (! (Millenium, 2000, XP und 7)) das, was im Quelltext steht (habe ja auch ca. 2 Jahre daran herumgemacht).

Bei Rechtsklick auf irgendeinen Thread (im Threadfenster) erscheint ein (Popup-)Menü, und sofern mehr als ein Prozessor(kern) vorhanden ist, sind auch die Menüoptionen "Prozessorenzuordnung" und "Vorzugsprozessor" aktiv(iert). Dann kann man nach Lust und Laune die Prozessorenzuordnungen und den Vorzugsprozessor ändern. Ist eine solche Zuordnung erfolglos, gibt es zwar keine Fehlermeldung (edit: doch, und zwar dann, wenn die betreffende Funktion ein false zurückliefert), aber ob sie erfolgreich war, kann man ja am (fehlenden) Umschalten der Häkchen erkennen.

Mangels höchstwertiger Hardware konnte ich das Programm allerdings nur bei 2 Prozessorenkernen prüfen, bei denen es funktioniert. Du hast wohl mehr, allerdings ist mir unklar, warum es bei mehr nicht funktionieren sollte.

Auch die Lazarusversion bricht bei mir nicht ab. Aber diese Diskussion ist nicht der Ort, mein vorgeschlagenes Programm auszuwerten. Schade, daß ich Dir nicht einmal marginal helfen konnte.

Edit: Könnte sein, daß ich den Fehler doch gefunden habe.


Bergmann89 - Sa 02.04.11 02:07

Hey,

mit der neuen Version deines Programm hab ich keine Fehlermeldungen und ich kann es ordentlich benutzen. Ich hab erstmal den Vorzugskern für die Threads festgelegt, das hat aber nichts geändert. Dann wollt ich die Zugehörigkeit der einzellen Kerne komplett selbst verteilen, aber sobald ich etwas einstelle, dann auf einen andern Thread wechsle und dann wieder auf den schon eingestellten zurück gehe sind die Einstellungen wieder gelöscht. Kann es sein, das Windows das wieder rückgängig macht?

MfG Bergmann.


Delphi-Laie - Sa 02.04.11 05:44

Hallo Bergmann89, danke für Deine PM (an dieser Stelle)! Schön, daß ich Dir nunmehr mit meinem Programm marginal weiterhelfen konnte (und nebenbei konnte ich dank Deiner einen weiteren Fehler in meinem Programm aufspüren und beseitigen). Endlich einmal war mein Programm zu etwas nützlich.

user profile iconBergmann89 hat folgendes geschrieben Zum zitierten Posting springen:
aber sobald ich etwas einstelle, dann auf einen andern Thread wechsle und dann wieder auf den schon eingestellten zurück gehe sind die Einstellungen wieder gelöscht. Kann es sein, das Windows das wieder rückgängig macht?


Ja, das sieht ganz danach aus - oder Windows nimmt diese Einstellungsveränderung gar nicht erst an; allerdings haben die betreffenden Funktionen einen bool(ean)schen Rückgabewert (just noch einmal nachgeschaut), den ich natürlich programmatisch auswertete. Eine Erfolglosmeldung scheint aber nicht zu erscheinen, vielleicht schwindelt Windows (auch) an dieser Stelle (mal wieder).

Wenn die Prozessoren-/Prozessorkernzuordnung mit Threads anderer Prozesse machst, wirst Du dieses Verhalten wahrscheinlich nicht finden (jedenfalls ist es nach meiner Beobachtung regelmäßig nicht der Fall, sogar mit den "systemnahen" Prozessen und deren Threads kann man fast nach belieben auf diese Weise umherspielen).

Der Fehler - wenn es überhaupt einer ist - muß also irgendwo anders liegen. Was jaenicke weiter oben schrieb, probierte ich übrigens auch einmal, und ich beobachtete es genauso: Bereits einzelne Threads werden von Windows - nach Möglichkeit - auf einzelne Prozessoren bzw. Prozessorkerne verteilt. Warum zum Teufel klappt das dann mit Deinem Programm nicht?

Vielleicht probierst Du es auch zunächst einmal mit einfachen Prozessorauslastungen ("while true do begin end" oder "repeat until false" o.ä.) in den Threads und bastelst, falls das klappt, daß die auf die einzelnen Prozessoren bzw. Prozessorkerne verteilt werden, allmählich Deinen Code in die Threadprozeduren? Das ist zwar eine sehr aufwendige Tippelschrittmethode, mag aber den Fehler aufzuspüren helfen. Derlei "evolutionäre" Programmierung ist bei mir als Nichtinformatiker im übrigen an der Tagesordnung.


bummi - Sa 02.04.11 08:27

Beim letzten Fall bei dem ich von so etwas gehört hat der TE das Execute der Threads aus dem Hauptprogramm aufgerufen, aber so einfach wird es bei Dir nicht sein?


Bergmann89 - Sa 02.04.11 23:33

Hey,

@Delphi-Laie: ich hab den verantwortlichen Code ja schon gefunden(siehe oben). Wenn ich das erzeugen des TTipTreeItems auskommentiere, dann arbeitet alles ohne Probleme auf 6 Kernen.
@bummi: Den Fehler kenn ich noch aus meinen ersten Schritten aus der Threadprogrammierung^^ Das hab ich natürlich nicht gemacht. Das Thread Objekt wird normal erstellt und ich hab auch mehrere Threads im Thread-Fenster, die alle arbeiten.

MfG Bergmann.


FrEaKY - So 03.04.11 05:07

user profile iconBergmann89 hat folgendes geschrieben Zum zitierten Posting springen:
Hey,

@Delphi-Laie: ich hab den verantwortlichen Code ja schon gefunden(siehe oben). Wenn ich das erzeugen des TTipTreeItems auskommentiere, dann arbeitet alles ohne Probleme auf 6 Kernen.
@bummi: Den Fehler kenn ich noch aus meinen ersten Schritten aus der Threadprogrammierung^^ Das hab ich natürlich nicht gemacht. Das Thread Objekt wird normal erstellt und ich hab auch mehrere Threads im Thread-Fenster, die alle arbeiten.

MfG Bergmann.

Es ist doch allgemein bekannt, dass die VCL nicht threadsicher ist...


jaenicke - So 03.04.11 05:59

user profile iconFrEaKY hat folgendes geschrieben Zum zitierten Posting springen:
Es ist doch allgemein bekannt, dass die VCL nicht threadsicher ist...
Schon, aber wo siehst du hier einen Zugriff darauf? :gruebel:


FrEaKY - So 03.04.11 06:07

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconFrEaKY hat folgendes geschrieben Zum zitierten Posting springen:
Es ist doch allgemein bekannt, dass die VCL nicht threadsicher ist...
Schon, aber wo siehst du hier einen Zugriff darauf? :gruebel:

Naja, offensichtlich muss es einen geben, weil wenn er die Zeile ausklammert geht es ja. Vielleicht liegt es am inherited Create; :?:


Martok - So 03.04.11 06:32

user profile iconFrEaKY hat folgendes geschrieben Zum zitierten Posting springen:
Naja, offensichtlich muss es einen geben, weil wenn er die Zeile ausklammert geht es ja. Vielleicht liegt es am inherited Create; :?:
Ich würde sagen, du hast es.


Delphi-Quelltext
1:
2:
3:
4:
class function TObject.NewInstance: TObject;
begin
  Result := InitInstance(_GetMem(InstanceSize));
end;

_GetMem mappt auf MemoryManager.GetMem mappt auf SysGetMem welches in grausamer Formatierung dieses tut:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
function SysGetMem(size: Integer): Pointer;
// Allocate memory block.
begin
{...}
  try
    if IsMultiThread then EnterCriticalSection(heapLock);
{...}
  finally
    if IsMultiThread then LeaveCriticalSection(heapLock);
  end;
end;


Solltest du also nicht FastMM verwenden, dürfte das das Problem sein. Diese wäre dann nämlich mit Lock-Free Structures, soweit ich das sehe. Und damit ist das eine ziemlich böse Stolperfalle :shock: Ich frag mich grade, bei wie vielen Programmen ich das Problem ungesehen auch habe. Man gut, dass FastMM auf neueren Delphis der Standard-MM ist...


jaenicke - So 03.04.11 08:10

Gut, das erklärt das dann. Da konnte ich leider nicht nachschauen, weil ich von den alten Delphiversionen nur noch die D7 PE (also ohne Quelltexte) im virtuellen PC habe.


Bergmann89 - So 03.04.11 17:03

Hey,

ich hab das hinzufügen zum Suchbaum mal in ein neues Projekt ausgelagert und 5 Threads erstellt die Zufallswerte ihn ihren eigenen Baum einfügen. Da läuft jeder Thread auf seinem eigenen Kern. Es ist zum Haare ausreißen -.-

MfG Bergmann.


Delphi-Laie - So 03.04.11 17:31

user profile iconBergmann89 hat folgendes geschrieben Zum zitierten Posting springen:
Da läuft jeder Thread auf seinem eigenen Kern. Es ist zum Haare ausreißen -.-


Warum, das wolltest Du doch?! Oder ist es zum Verzweifeln, daß es beim Suchbaum funktioniert, jedoch nicht mit dem Anliegen, womit Du diese Diskussion eröffnetest?


Bergmann89 - So 03.04.11 17:41

Hey,

ich benutz den Suchbaum in meinem Programm, da geht er nicht. In dem Testprojekt geht er aber. Das ist zum Haare ausreißen...

MfG Bergmann