![]() |
AW: Multithreading
Wenn es eine globale Variable ist, dann streiten sich die Kerne darum.
Aber mir war so, als wären es Threadvars ... [edit] Bin erschreckt, aber ErrorAddr und RandSeed sind wirklich
Delphi-Quellcode:
anstatt
var
Delphi-Quellcode:
:shock:
threadvar
|
AW: Multithreading
Hmmm blöd gefragt ich dachte wenn es eine 0815 globale Variable ist, dann greifen die Threads einfach direkt darauf zu, und es kann passieren das da dann Müll rauskommt wenn einer schreibt und einer liest und nicht das sich das ganze dann so verhält wie wenn es in einer criticalsection stehen würde?
dh meiner Meinung nach dürfte eine "normale" variable den Thread nicht verlangsamen sondern es kann passieren das irgendwelche komischen Fehler passieren (wenn die nicht in einer CriticalSection sind) |
AW: Multithreading
Das Problem entsteht durch die unterschiedlichen Caches der Kerne. Die CPU liest ja nicht von und schreibt ja nicht in den tatsächlichen RAM-Bereich, sondern wickelt das über den Cache ab. Dazu wird ein Bereich des RAM in den Cache transferiert (die sogenannte Cache Line) und später wieder geschrieben. Die dabei unweigerlich entstehenden Zugriffsprobleme werden durch das Sperren des zugehörigen RAM-Bereich vermieden. Deswegen müssen die Kerne manchmal eben warten - auch beim Lesen.
Wegen der Größe der Cache Line (i.d.R. 64 Bytes) kommt der Effekt auch zum tragen, wenn nicht mal nur dieselbe Variable verwendet wird, sondern sich z.B. auch zwei Variablen in derselben Cache Line befinden. |
AW: Multithreading
Die Threads prinzipiell ja, außer im Maschinencode/Assembler gibt man z.B. LOCK an ( MOV a, b -> LOCK MOV a, b ).
Aber dennoch kann nicht jeder Thread Kern einfach so gleichzeitig auf jegliche "externe" Hardware zugreifen, wie z.B. den RAM. zugeteilte Speicherseiten, vorhandener Cache Cacheline usw ... macht jeder Prozessor-Architektur eventuell auch noch jeweils anders. Es gibt ja auch nicht für jeden Kern ein eigenes Kabel zu jedem Speicherchip. Oder anders gesagt, so lange jeder Thread möglichst GARNICHTS mit anderen Threads teilt, dann ist es optimal. (bzw. auch nichts, was nur zufällig in der Nähe liegt) |
AW: Multithreading
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:
Zum Thema: Davon abgesehen, dass Random nicht threadsafe ist, hast du hier den klassischen Fall von Code, dessen Multithreadgeschwindigkeit davon gebremst wird, dass shared memory immer übern RAM zu jedem Kern wandern muss. Auch wenn hier RandSeed, was innerhalb von Random genutzt wird, nicht irgendwie abgesichert wird, ist es doch so, dass jeder Schreibvorgang der CPU signalisiert, dass der Speicher geschrieben wurde, was dazu führt, dass jeder andere Kern, der diesen Wert liest, ihn erst wieder aus dem RAM lesen muss. In deinem Fall führt das dazu, dass die verschiedenen Kerne massiv davon gebremst werden, dass der Wert dieser globalen Variable tausendfach über den RAM zwischen den Kernen hin und her wandert. Im VTune kann man das sehr schön sehen - siehe Anhang |
AW: Multithreading
Okay Danke das ist echt "krass" das durch den Cache das sooo extremst langsam wird dann.
jeweils 100 Durchgänge auf 8 Kerne - dh mit 8 Threads dauert ein einzelner Durchgang ca 16 mal so lange - (er sollte ja 8 mal schneller sein und braucht doppelt so lang) Threading (1 Threads) : 3199,55 ms Threading (2 Threads) : 5985,58 ms Threading (4 Threads) : 6630,61 ms Threading (8 Threads) : 6957,30 ms |
AW: Multithreading
Hab jetzt nur zum Gegencheck den Ransom ähmm Random als lokal funktion eingebaut
Delphi-Quellcode:
und siehe da nun stimmen die Zeiten zu dem was ich mir vorgestellt habe :D
procedure internalExecute;
var i : integer; x : Byte; dummy : int64; RandSeed : integer; function myRandom(const ARange: Integer): Integer; asm { ->EAX Range } { <-EAX Result } PUSH EBX XOR EBX, EBX IMUL EDX,[EBX].RandSeed,08088405H INC EDX MOV [EBX].RandSeed,EDX MUL EDX MOV EAX,EDX POP EBX end; begin QueryPerformanceCounter(dummy); RandSeed := dummy; x := 9; for i:=1 to 1000000 do begin x := x xor (myRandom(23223) mod 255); //x := x xor (i mod 255); end; //*) SetEvent(fWaitFinish); end;
Code:
Damit ich es nochmal verstehe das genau das das Problem ist.
Threading (1 Threads : 3289,73 ms
Threading (2 Threads : 1670,74 ms Threading (4 Threads : 851,50 ms Threading (8 Threads : 426,44 ms Kern 1 lädt die 64Byte wo die variable RandSeed ist in die CacheLine des Kern1 Kern 2 ... Kern 4 machen das gleiche da nun einer der Kerne die Variable Randseed ändert, muss der Kern nun die 64Byte aus der CacheLine wieder zurück in den RAM speichern - und dabei markiert er bei allen anderen Kernen nun das die 64 Byte die die anderen Kerne in der Cacheline haben ungültig sind und diese müssen die nun erneut laden.
|
AW: Multithreading
Inzwischen kann man in Delphi ein eigenes Random registrieren.
Dort könnte man Eines mit TheadVar bauen. (und wenn noch RandSeed=0, dann darin zuerst noch ein Randomize) |
AW: Multithreading
Delphi-Quellcode:
hat das in
System.TMonitor
Delphi-Quellcode:
stehen, aber da kommt man wegen
CacheLineSize
Delphi-Quellcode:
nicht so einfach ran. Intern wird das benutzt, damit ein
strict protected
Delphi-Quellcode:
Record immer mindestens so groß ist wie eine Cache Line.
TMonitor
Du kannst dir aber den entsprechenden Code in
Delphi-Quellcode:
oder
GetCacheLineSize
Delphi-Quellcode:
abgucken.
GetCacheSize
|
AW: Multithreading
Wie läuft es mit den Zeiten, wenn
Delphi-Quellcode:
als Erstes im Execute, mit originalem Random.
SetThreadAffinityMask(GetCurrentThread {oder Self.Handle}, $00000001);
Gilt nur, wenn du maximal 64 Kerne hast, sonst fängt man eventuell auch noch mit Prozessorgruppen an. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 14:35 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz