Page 1 of 2 1 2 LastLast
Results 1 to 15 of 18

Thread: Trage thread

  1. #1

    Trage thread

    Beste mensen,

    Ik heb een algoritme geschreven dat ik door een thread wil laten uitvoeren.
    De te berekenen input data deel ik op in groepen gelijk aan het aantal processor cores. Daarna laat ik deze gegevens door een gelijk aantal instanties van deze thread bewerken.

    We hebben alleen het probleem dat we hiermee geen 100% processor belasting halen, maar eerder ergens in de buurt van 30%.

    Na wat onderzoek lijkt het probleem te komen door het lokaal declareren van dynamische arrays. Maar ook nadat van een stuk code al het dynamisch geheugen globaal is gemaakt blijkt dit niet de oplossing te zijn.

    Op welke manier kan ik dit probleem oplossen? Waardoor wordt dit probleem veroorzaakt?

    Ik wil zo min mogelijk aanpassingen doen aan de bestaande algoritme code. Zeker sommige recursieve stukken zijn lastig aan the passen (in het geval van dynamisch geheugen globaal maken).

    Hieronder een stuk code voorbeeld code. Het wisselen tussen DoFill1 en DoFill2 in de execute illustreert het probleem.

    Alvast bedankt,
    Beijert

    Code:
    type
      T2DArray = Array of Array of Double;
      P2DArray = ^T2DArray;
    
      TMultiThread = class(TThread)
      private
        da, db: Double;
        arr: T2DArray;
        procedure DoFill1(a: P2DArray);
        procedure DoFill2(a: P2DArray);
      protected
        procedure Execute; override;
      end;
    
    ...
    
    procedure TMultiThread.DoFill1(a: P2DArray);
    var
      tmp: T2DArray;
      i, j: Integer;
    begin
      SetLength(tmp, Length(a^), Length(a^[0]));
      for i := 0 to Length(tmp) - 1 do
        for j := 0 to Length(tmp[0]) - 1 do
          tmp[i][j] := da * db;
    
      CopyMemory(a, @tmp, SizeOf(tmp));
    end;
    
    procedure TMultiThread.DoFill2(a: P2DArray);
    var
      i, j: Integer;
    begin
      for i := 0 to Length(a^) - 1 do
        for j := 0 to Length(a^[0]) - 1 do
          a^[i][j] := da * db;
    end;
    
    procedure TMultiThread.Execute;
    begin
      SetLength(arr, 8, 8);
    
      da := 1.123;
      db := 3.345;
    
      while not Terminated do
      begin
        // dit geeft een lage processor belasting
        DoFill1(@arr);
    
        // dit geeft een hoge processor belasting
        //DoFill2(@arr);
      end;
    end;
    
    ...
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      TMultiThread.Create(false);
    end;

  2. #2
    Silly member NGLN's Avatar
    Join Date
    Aug 2004
    Location
    Werkendam
    Posts
    5,133
    De doelstelling is dus om je code over meerdere cores te verdelen? Wel, ik weet niet hoe je dat kunt aansturen of überhaupt kunt beïnvloeden, maar het aanmaken van meerdere threads in de hoop dat elke core een thread te verhappen krijgt behoort in ieder geval niet tot de mogelijkheden. Het zal een combinatie zijn van compiler, memorymanager, delphi versie, OS, etc... Enfin, ik zou niet weten hoe dat moet.

    De thread uit bovenstaande code heeft in dit geval géén invloed op jouw testresultaat, aangezien alles in de mainthread wordt uitgevoerd. Uitleg: Beide routines, DoFill en DoFill2, worden uitgevoerd in de MainThread, en n?¡et in een TMultiThread.Execute. Het enige wat jouw TMultiThread doet staat in de Execute, en dat is:
    - het instellen van het formaat van een array,
    - het toewijzen van twee variabelen (die arr, da en db variabelen zijn overigens ook géén onderdeel van de thread, maar zijn onderdeel van de mainthread),
    - het continue aanroepen van een routine in de mainthread.

    Het verschil tussen beide DoFill routines is dat bij DoFill1 het array op de stack wordt bewerkt (wat doorgaans sneller is), en dat bij DoFill2 het array in het gedeelde geheugen wordt bewerkt.

    Kortom: de testopstelling test niet hetgene je denkt te testen, en het threadgebruik is eigenlijk niet zoals het hoort.

    Overigens zou ik het verschil in processorbelasting niet kunnen verklaren; Aangezien de code continue wordt uitgevoerd zou het volgens mij juist gelijk moeten zijn in beide gevallen, maar is de ene routine gewoon eerder klaar als de andere. Of je code w??rdt al over meerdere cores verdeelt, want hoe meet jij die processorbelasting? Is dat de belasting per core of de belasting van de totale processor?

    //En dit alles naar mijn bescheiden mening. Mocht het verkeerd zijn, dan wordt ook ik graag op de vingers getikt.
    Last edited by NGLN; 18-Mar-09 at 20:04.
    (Sender as TNLDUser).Signature := 'Groeten van Albert';

  3. #3
    De code wordt netjes in de thread uitgevoerd.

    Het regelmatig geheugen van de heap alloceren (SetLength) en vrijgeven (aan het einde van de method) kost tijd. Daardoor haal je niet je 100%. Je 2e methode doet dat niet en geeft je kennelijk ook het juiste resultaat. Wat is je vraag dan nog?

    PS: Een dynamisch array is al een reference. Het is dus onnodig om daar nog weer een pointer heen te hebben.
    We adore chaos because we like to restore order - M.C. Escher

  4. #4
    Stijn Sanders develyoy's Avatar
    Join Date
    Jun 2008
    Location
    GentBrugge, Belgi?½
    Posts
    1,046
    Ik heb gemerkt dat ik op een multi-CPU computer niet zomaar op meer dan 1 CPU 100% kan halen. Het werk lijkt zich wel te spreiden, maar als je de beetjes samentelt dan kom je maar aan 100% op 1 CPU. Ik had wel in 1 scenario gemerkt dat ik plots wel meer dan 1 CPU aan het werk kon zetten, maar had toen geen tijd om specifiek daarna opzoek te gaan. Voorlopig denk ik omdat dat specified project veel aparte dingen in aparte DLL's deed... Mogelijk is er een manier om Windows te vertellen dat hij wel meer dan 1 CPU aan het werk mag zetten voor het proces, misschien met manifests, maar hoe precies heb ik nog niet gevonden.

  5. #5
    Silly member NGLN's Avatar
    Join Date
    Aug 2004
    Location
    Werkendam
    Posts
    5,133
    Volgens de WinAPI help kun je met SetProcessAffinityMask, SetThreadAffinityMask en SetThreadIdealProcessor e.e.a. beïnvloeden, maar de OS-scheduler bepaalt uiteindelijk toch wat er gebeurt.

    @LL:
    Hmm, daar had je me al wel eerder op kunnen wijzen...
    Enfin, ik dacht altijd (en heb dat hier ook altijd verkondigd, bijv hier) dat alles buiten de Execute routine van een thread tot de mainthread behoort. Ik heb zojuist (eindelijk maar eens ) de WinAPI help hierover gelezen en kom nu tot de volgende stelling: Een thread is geen "eigenaar" van data of code, het process is dat wel. Alle code en data van een process is voor alle threads binnen dat process tegelijkertijd beschikbaar. Maar daarbij moet je oppassen met niet tegelijkertijd: 1. hetzelfde geheugen(adres) beschrijven, 2. dezelfde GDI-objects bewerken, en dus ook niet 3. de VCL gebruiken. Klopt het zo een beetje?
    (Sender as TNLDUser).Signature := 'Groeten van Albert';

  6. #6
    Quote Originally Posted by NGLN View Post
    ...dat alles buiten de Execute routine van een thread tot de mainthread behoort...
    In dit geval zullen Forms dus freezen als je je thead een andere procedure laat uitvoeren dan de execute? (wel door execute aangeroepen dan)
    Ben ik wel benieuwd naar omdat ik ook wel met threads bezig ben de laatste tijd. K maak ook wel even een testje

  7. #7
    Bij een TThread speelt het leven van de thread zich af in de Execute method. Als die execute method afgelopen is stopt de thread. Maar alles wat je vanuit de Execute method aanroept wordt uitgevoerd door die thread op dat moment. De plaats van de code maakt daarbij niet uit. Als je vanuit de Execute een method van een form of een component aanroept wordt dat nog steeds uitgevoerd door die thread. TThread.Synchronize zorgt er voor dat de executie vanuit de thread tijdelijk wordt overgedragen naar de main thread.

    Als je je programma uitvoert en je met de debugger stap voor stap door je code loopt is er een 'pointer' die de huidige regel aangeeft waar je bent met uitvoeren. Een tweede thread zou je je kunnen voorstellen als een extra 'pointer' die aangeeft waar je bent met uitvoeren. Beide instructie pointers kunnen tegelijk code uitvoeren en daarbij maakt het niet uit waar die code staat.

    Dat er dingen fout kunnen gaan als meerdere threads tegelijk een stuk data benaderen is een feit. Aangezien GDI objecten en de VCL ook bestaan uit data die je meestal niet door meerdere threads kan laten benaderen klopt het inderdaad dat je daar voor op moet passen.
    We adore chaos because we like to restore order - M.C. Escher

  8. #8
    En dat klopt ook precies met de uitkomsten van mn testje dat ik aan het doen was
    Met mn testje haalde ik trouwens met 3 threads een processorbelasting van 80%, wat denk ik veroorzaakt werd door de vele disk I/O van de threads. Maar t kwam wel overduidelijk boven de 50% uit (k heb een dualcore btw).

  9. #9
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357
    Let op: het processor belasting getalletje is niet alles, en slechts een ruwweg een benadering. Je kan dit heel goed zien als je zowel XP als Vista gebruikt, beide geven andere getalletjes voor hetzelfde programma.

    (voor de liefhebbers: Vista wat completely fair scheduler extensies?)

  10. #10
    Bedankt voor alle reacties,

    Quote Originally Posted by Lord Larry View Post
    ... Het regelmatig geheugen van de heap alloceren (SetLength) en vrijgeven (aan het einde van de method) kost tijd. Daardoor haal je niet je 100%.
    Dat is inderdaad het probleem, stom dat ik daar zelf niet aan had gedacht. Het betekent jammer genoeg wel dat ik flink wat code moet aanpassen.

    Quote Originally Posted by NGLN View Post
    Volgens de WinAPI help kun je met SetProcessAffinityMask, SetThreadAffinityMask en SetThreadIdealProcessor e.e.a. beïnvloeden, maar de OS-scheduler bepaalt uiteindelijk toch wat er gebeurt.
    Ik gebruikte eerst WMI om het aantal cores te bepalen, maar met GetProcessAffinityMask is dat een stuk minder omslachtig en sneller. Als de OS-scheduler niet bevalt dan zou ik inderdaad gebruik kunnen maken van SetProcessAffinityMask en SetThreadAffinityMask om het geheel op snelheid te krijgen.

  11. #11
    Informatieve thread !

    @NGLN dank voor het noemen van die API's, ik wist niet dat dit in W2000 Pro kon met D7 uses Windows..

    Ik heb het net ff getest met de DoFill2 versie (if you don't mind ) en het warmt e.e.a. aardig op hier ! 90% op dualcore Acertje.

    Recept voor gratis vloerverwarming: men neme bovenstaande code op bij een testformuliertje Form1. Zet de decl in de interface en bewaar twee van die instances in het formulier:

    Code:
      public
        th1: TMultiThread;
        th2: TMultiThread;
    En dan onder die button dus dit,

    Code:
      th1:=TMultiThread.Create(false);
      SetThreadAffinityMask(th1.Handle,1);
      th2:=TMultiThread.Create(false);
    Als je alleen de eerste start, loopt ie 100% op processor 1. Zie MSDN - affinity mask

    http://msdn.microsoft.com/en-us/libr...7(SQL.80).aspx

    Dit is het bitpatroon van de tweede parameter:

    Dec Bin mask Allow processors
    1 00000001 0
    3 00000011 0 and 1
    7 00000111 0, 1, and 2

    etcetera..


    Lx
    Minstens ?®?®n hobby naast programmeerwerk is echt noodzakelijk

  12. #12
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357
    Quote Originally Posted by Lord Larry View Post
    Het regelmatig geheugen van de heap alloceren (SetLength) en vrijgeven (aan het einde van de method) kost tijd. Daardoor haal je niet je 100%. Je 2e methode doet dat niet en geeft je kennelijk ook het juiste resultaat. Wat is je vraag dan nog?
    Maar setlength e.d. worden door de memory manager uitgevoerd die grotendeels geheel in-process is, dus de CPU verstookt in setlength zou gewoon in het percentage moeten meetellen.

    Afhankelijk van de memory manager kan het echter dat grote blokken direct bij het OS gealloceert/gedealloceert worden, en dat je dat ziet. En misschien dat als je sharemem gebruikt, het geheel out-of-process is.

  13. #13
    Is je algoritme eigenlijk ingewikkeld genoeg dat je voordeel gaat halen van multi-cores? Je kunt ook gewoon memory-access bound zijn

  14. #14
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357
    Met floating point ben je gauwer CPU bound. De instructie latencies (vooral voor deling) zijn langer.

  15. #15
    Alles is CPU-bound zolang je niet op de cache zit. Valt wel mee die floating point overigens, zeker als je twee versies gaat vergelijken waarbij de ene versie memory-management gebruikt en de ander niet. Ik vraag me trouwens af of dat wel zo safe is in concurrent threads, die setlength. Maar dan nog.. vroeger was het wel zo: de calloc en malloc waren veel sneller dan de log en de sin. Ik zit er al heel lang in.. sinds ze die X87 num coprocessor hebben geintegreerd (486) is float en double heel snel geworden. Probeer maar eens een FFT'tje die geschreven is in 1990 of zo. Veel sneller dan de houtje-touwtje spul die ze er nu voor gebruiken !!


    Lx
    Minstens ?®?®n hobby naast programmeerwerk is echt noodzakelijk

Page 1 of 2 1 2 LastLast

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •