Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Tparallel und Bitmap-Bearbeitung (https://www.delphipraxis.net/182796-tparallel-und-bitmap-bearbeitung.html)

Harry Stahl 19. Nov 2014 00:17

Tparallel und Bitmap-Bearbeitung
 
Ich würde gerne die TParallel-Library einsetzen, um verschiedene Bitmap-Routinen schneller zu machen.

Ich habe hier versucht, das mal für eine 24-Bit-Bitmap zu implementieren (Funktion, welche abhängig von einem Wert eine Bitmap aufhellt bzw. abdunkelt):
Delphi-Quellcode:
procedure Helligkeit(Bitmap: TBitmap; Const Original : TBitmap; Value: integer; IgnoreWhite: Boolean);
var
  x,y    : integer;
  Ziel   : ^TRGBTriple;
  Quelle : ^TRGBTriple;
  n      : byte;
  ar     : array[0..255] of byte;
  RGBAQuelle, RGBAZiel: pRGBALine;
  OK: Boolean;
begin
  n := abs(value);

  if value > 0 then
    for x := 0 to 255 do if integer(x + n) > 255 then ar[x] := 255 else ar[x] := x + n
  else
    for x := 0 to 255 do if integer(x - n) < 0 then ar[x] := 0 else ar[x] := x - n;

    for y := 0 to Bitmap.Height-1 do begin
      Ziel  := Bitmap.Scanline[y];
      Quelle := Original.Scanline[y];

      TParallel.For(0, Bitmap.Width-1, procedure (L: Integer) // <-- Hier neu eingebaut
      begin
        if IgnoreWhite then begin
          OK := (Quelle^.rgbtBlue <> 255) or (Quelle^.rgbtGreen <> 255) or (Ziel^.rgbtGreen <> 255);
        end else begin
          OK := True;
        end;

        if OK then begin
          Ziel^.rgbtBlue := ar[Quelle^.rgbtBlue];
          Ziel^.rgbtred  := ar[Quelle^.rgbtred];
          Ziel^.rgbtGreen := ar[Quelle^.rgbtGreen];
        end;

        inc(Ziel);
        inc(quelle);
      end)
    end;
end;
Leider kriege ich immer Zugriff-Exceptions (Access violations), bzw. Bild ist verzerrt und Programm hängt sich auf.

Jemand eine Idee, wie man das richtig macht? Muss ich den Zugriff auf das Bitmap schützen, wenn ja, wie am besten?

Sir Rufo 19. Nov 2014 02:04

AW: Tparallel und Bitmap-Bearbeitung
 
Nun überleg doch mal, wofür man das
Delphi-Quellcode:
L
benutzen könnte, wenn man mal unterstellt, dass
Delphi-Quellcode:
Ziel
und
Delphi-Quellcode:
Quelle
ein
Delphi-Quellcode:
Array
ist (ist es).

Und gleichzeitig frage dich, ob es gut ist, wenn man bei einer parallelen Bearbeitung überall den Bezugspunkt einfach so verschiebt
Delphi-Quellcode:
Inc(Ziel); Inc(Quelle);
.

Das kann man fast vergleichen mit "Wenn ein Ei 5 Minuten gekocht werden muss, wie lange müssen dann 10 Eier kochen?" :mrgreen:

himitsu 19. Nov 2014 02:05

AW: Tparallel und Bitmap-Bearbeitung
 
Scanline: Ist das auch wirklich ein 32-Bit-Bitmap?

Und man durfte noch nie nicht-threadsichere Befehle in einem Thread ausführen.
  • Inc z.B.

Außerdem sind alle deine lokalen Variablen sowas wie globale Variablen, welche in allen Threads "gleich" sind.
  • B muß lokal in die Thread-Prozedur
  • Quelle/Ziel darf nicht geändert werden, also auch eine lokale Thread-Variable und dann LokalQuelle:=Quelle+L;
  • Pro Thread nur ein einziger Wert macht das nicht schneller, sondern sehr viel langsamer, da viele Threads. -> Anzahl=Breite*Höhe
    Wenn, dann eher pro Line, oder besser für mehrere Lines, ein Thread.


Eier? :lol: Oder ...
Wenn ein Kuchen bei 200°C 40 Minuten braucht, wie lange braucht er dann bei 800°C?

Sir Rufo 19. Nov 2014 02:15

AW: Tparallel und Bitmap-Bearbeitung
 
@himitsu

Das Argument "viele Threads" stimmt nicht, es sind "viele Aufgaben" die mit einem ThreadPool abgearbeitet werden. Da dürften nach einer gewissen "Warmlaufphase" nur eine Handvoll Threads aktiv sein.

himitsu 19. Nov 2014 02:17

AW: Tparallel und Bitmap-Bearbeitung
 
Ja, aktiv sind gleichzeitig immer nur ein paar, aber insgesammt sind es sehr Viele.
Vermutlich also mehr Verwaltungaufwand, als Zeitersparnis.


Vorher gab es Einen Typen, der holte sich alle Infos und arbeitete das dann nacheinander ab.
Jetzt gibt es vielleicht 8 Leute, die rennen gleichzeitig zu einen Verwalter Hauptthread, holen sich durcheinanderredend irgendeine Arbeitsposition bearbeiten ein Pixel schreiben gleichzeitig in zwei Variablen rein und rennen dann wieder zum Pool-Verwalter, fragen ob noch ein Thread da ist, fragen wieder gleichzeitig den Verwalter Hauptthread und bearbeiten irgendin ein Pixel, bis alle Pixel Threads durch sind.

Sir Rufo 19. Nov 2014 02:26

AW: Tparallel und Bitmap-Bearbeitung
 
Es sind eben nicht insgesamt viele, es sind und bleiben eine Handvoll Threads, die, wenn einmal erzeugt, bis zum Ende des ThreadPools aktiv/schlafend bleiben. Diese Threads holen sich einfach die nächste Aufgabe, verarbeiten die und es geht wieder von vorne los. Da ist kaum ein Unterschied, ob ich jetzt eine Aufgabe mit 10 Einzelschritten oder 10 Aufgaben mit jeweils 1 Einzelschritt übergebe.

himitsu 19. Nov 2014 03:24

AW: Tparallel und Bitmap-Bearbeitung
 
OK, dann sind es eben massig anonyme Methoden (1), welche in einem Pool warten und von einer Hand voll Threads abgearbeitet werden.

1:
* kurzer Code von nichtmal annähern einer Millisekunde Rechenzeit
* der jeweils nur ein einziges Pixel verarbeitet

Die Verwaltung und Synchronisierung dieses Pools ist garantiert langsamer, als die eingesparte Zeit.
Über die Verarbeitung einer ganzen oder mehrere Zeilen, würde jede Methode länger rechnen und der Tool müsste gleichzeitig weniger verwalten.

Dejan Vu 19. Nov 2014 04:54

AW: Tparallel und Bitmap-Bearbeitung
 
Ich würde die Bearbeitung für jede ScanLine in einen separaten Thread verschieben, also vielleicht so:
Delphi-Quellcode:
TParallel.For(0, Bitmap.Height-1, procedure (Y: Integer)
var
  Ziel : ^TRGBTriple;
  Quelle : ^TRGBTriple;
  OK: Boolean;

begin
  Ziel := Bitmap.Scanline[y];
  Quelle := Original.Scanline[y];
,,,
Grund:
1. Größere Abschnitte (vielleicht kann man die noch größer machen, jeder Thread X Scanlines auf einmal)
2. Keiner tritt dem anderen auf die Füße.

Die Vergleiche mit Eiern und Kuchen sind zwar boxkomisch, aber hier ist der Denkfehler ja der, das die Variablen unkontrolliert verwendet werden, und das sehe ich bei dem Eierkochenkuchenbackenvergleich nicht.

Diese TParallel-Library ist kein Hexenwerk und zaubern kann sie auch nicht. Das ist ein ziemlich banaler, aber sehr elegant zu verwendender Threadpool. Alles, was man über Threads weiß, muss man auch hier beherzigen. Wenn man die einzelnen Jobs nicht allzu granular aufteilt (1 Pixel ist SEHR granular) und den Scope/Zugriff der gemeinsam verwendeten Variablen beachtet (Füße, blaue Flecken und so), muss man sich wohl über die Anzahl der CPU-Kerne keine Gedanken mehr machen. Aber über den ganzen Rest schon.

himitsu 19. Nov 2014 10:14

AW: Tparallel und Bitmap-Bearbeitung
 
Ja, Ziel/Quelle in jeden Thread verschieben und zugleich hoffen, daß Scanline threadsafe ist.

Dejan Vu 19. Nov 2014 11:56

AW: Tparallel und Bitmap-Bearbeitung
 
Zitat:

Zitat von himitsu (Beitrag 1280290)
Ja, Ziel/Quelle in jeden Thread verschieben und zugleich hoffen, daß Scanline threadsafe ist.

=> Ansonsten vorher in ein Array packen.

himitsu 19. Nov 2014 12:32

AW: Tparallel und Bitmap-Bearbeitung
 
Man kann es auch Mathematisch erledigen ... wenn man weiß wie die Daten im Bitmap gespeichert werden.

Das Windows-Bitmap wird von unten nach oben gespeichert.
* Scanline auf die letzte Zeile
* und dann liegen die Linies hinterienander
* zwischenden Lines muß man nur noch das Align beachten, also jeweils auf 4 Byte aufrunden, aber bei 32-Bit-Bitmaps fällt das nicht auf

Delphi-Quellcode:
P: TPixelAarray; // array[0..z] of TRGB;
P := Scanline[Bitmap.Height - 1];
Pixel := P[(Bitmap.Height - y - 1) * Image.Width + x];

Harry Stahl 19. Nov 2014 18:37

AW: Tparallel und Bitmap-Bearbeitung
 
Zitat:

Zitat von himitsu (Beitrag 1280260)
Scanline: Ist das auch wirklich ein 32-Bit-Bitmap?

Scanline kannst Du auch mit einem 24-Bit-Bitmap verwenden, Du musst halt nur berücksichtigen, dass hier auf 3 Bit gezeigt wird (bei 32-Bit auf 4 Bit).

Zitat:

Zitat von Sir Rufo (Beitrag 1280259)
Und gleichzeitig frage dich, ob es gut ist, wenn man bei einer parallelen Bearbeitung überall den Bezugspunkt einfach so verschiebt
Delphi-Quellcode:
Inc(Ziel); Inc(Quelle);
.

OK, in einem Thread ist das wirklich keine gute Idee.

Ich versuche mal einen anderen Ansatz, das mit TTask zu beschleunigen, also einmal den oberen Teil der Bitmap berechnen, dann den unteren Teil, beides halt (fast) gleichzeitig. Da ich in den jeweiligen Threads nur lokale Variablen verwende und auf das zu verändernde Bitmap auf unterschiedliche Bereiche zugreife, dürften doch eigentlich keine Konflikte entstehen, oder?

Dennoch bekommen ich eine Zugriffsverletzung, wenn ich auch den 2. Task starte. Die einzelnen Tasks selber Funktionieren, wenn man nur einen startet, dann macht er, was er soll.

Wo könnte denn jetzt hier der Fehler liegen?

Hier der Source-Code:

Delphi-Quellcode:
    H1 := Bitmap.Height div 2; // obere Hälfte der Bitmap
    H2 := H1+1; // Startpunkt für die untere Hälfte

    SetLength(myTasks, 2);

       // Task 1 verändert die obere Hälfte des Bitmaps
    myTasks[0] := TTask.Create(procedure ()
    var
      Ziel, Quelle : ^TRGBTriple;
      x, y: Integer; OK: Boolean;
      begin
        for y := 0 to H1 do begin
          Ziel  := Bitmap.Scanline[y];
          Quelle := Original.Scanline[y];

          for x := 0 to (Bitmap.Width-1) do begin
            if IgnoreWhite then begin
              OK := (Quelle^.rgbtBlue <> 255) or (Quelle^.rgbtGreen <> 255) or (Ziel^.rgbtGreen <> 255);
            end else begin
              OK := True;
            end;

            if OK then begin
              Ziel^.rgbtBlue := ar[Quelle^.rgbtBlue];
              Ziel^.rgbtred  := ar[Quelle^.rgbtred];
              Ziel^.rgbtGreen := ar[Quelle^.rgbtGreen];
            end;

            inc(Ziel);
            inc(Quelle);
          end;
        end;
      end
      );

    myTasks[0].Start;

    // Task 2 verändert die untere Hälfte des Bitmaps
    myTasks[1] := TTask.Create(procedure ()
    var
      Ziel, Quelle : ^TRGBTriple;
      x, y: Integer; OK: Boolean;
      begin
        for y := H2 to Bitmap.height-1 do begin
          Ziel  := Bitmap.Scanline[y];
          Quelle := Original.Scanline[y];

          for x := 0 to (Bitmap.Width-1) do begin
            if IgnoreWhite then begin
              OK := (Quelle^.rgbtBlue <> 255) or (Quelle^.rgbtGreen <> 255) or (Ziel^.rgbtGreen <> 255); //<-- Hier kommt eine Access-Violation
            end else begin
              OK := True;
            end;

            if OK then begin
              Ziel^.rgbtBlue := ar[Quelle^.rgbtBlue];
              Ziel^.rgbtred  := ar[Quelle^.rgbtred];
              Ziel^.rgbtGreen := ar[Quelle^.rgbtGreen];
            end;

            inc(Ziel);
            inc(Quelle);
          end;
        end;
      end
      );

    myTasks[1].Start;

    TTask.WaitForAll(myTasks);
Nachtrag: Manchmal läuft es ohne Access-Verletzung durch. Gemessen braucht es dann bei einem 3872 x 2592 Pixel großem Bitmap nur ca. 123 MS, während ohne Parallele Bearbeitung es ca. 185 MS dauert. Wäre also schon eine hilfreiche Beschleunigung, wenn man das stabil hinbekommen könnte.

Sir Rufo 19. Nov 2014 19:00

AW: Tparallel und Bitmap-Bearbeitung
 
Zum einen hier mal der Vergleich zwischen Normal und Parallel.

Dazu habe ich die
Delphi-Quellcode:
procedure
einmal so umgeschrieben, dass die auch vernünftig parallelisiert werden kann.
Delphi-Quellcode:
unit BitmapProcessing;

interface

uses
  Winapi.Windows,
  Vcl.Graphics;

procedure HelligkeitNormal( Bitmap: TBitmap; Const Original: TBitmap; Value: integer; IgnoreWhite: Boolean );
procedure HelligkeitParallel( Bitmap: TBitmap; Const Original: TBitmap; Value: integer; IgnoreWhite: Boolean );

implementation

uses
  System.Threading;

type
  PRGBTripleArray = ^TRGBTripleArray;
  TRGBTripleArray = array [0 .. 4096] of TRGBTriple;

procedure HelligkeitNormal( Bitmap: TBitmap; Const Original: TBitmap; Value: integer; IgnoreWhite: Boolean );
var
  x, y: integer;
  Quelle, Ziel: PRGBTripleArray;
  n: byte;
  ar: array [0 .. 255] of byte;
  LIdx: integer;
begin
  Bitmap.Assign( Original );

  n := abs( Value );

  if Value > 0
  then
    for x := 0 to 255 do
      if integer( x + n ) > 255
      then
        ar[x] := 255
      else
        ar[x] := x + n
  else
    for x := 0 to 255 do
      if integer( x - n ) < 0
      then
        ar[x] := 0
      else
        ar[x] := x - n;

  for y := 0 to Bitmap.Height - 1 do
    begin
      Ziel := Bitmap.Scanline[y];
      Quelle := Original.Scanline[y];

      for LIdx := 0 to Bitmap.Width - 1 do
        begin

          if not IgnoreWhite or ( ( Quelle[LIdx].rgbtBlue <> 255 ) or ( Quelle[LIdx].rgbtGreen <> 255 ) or ( Ziel[LIdx].rgbtGreen <> 255 ) )
          then
            begin
              Ziel[LIdx].rgbtBlue := ar[Quelle[LIdx].rgbtBlue];
              Ziel[LIdx].rgbtRed := ar[Quelle[LIdx].rgbtRed];
              Ziel[LIdx].rgbtGreen := ar[Quelle[LIdx].rgbtGreen];
            end;

        end;

    end;
end;

procedure HelligkeitParallel( Bitmap: TBitmap; Const Original: TBitmap; Value: integer; IgnoreWhite: Boolean );
var
  // x,
  y: integer;
  Quelle, Ziel: PRGBTripleArray;
  n: byte;
  ar: array [0 .. 255] of byte;
  // LIdx: integer;
begin
  Bitmap.Assign( Original );

  n := abs( Value );

  if Value > 0
  then
    // for x := 0 to 255 do
    TParallel.&For( 0, 255,
        procedure( x: integer )
      begin
        if integer( x + n ) > 255
        then
          ar[x] := 255
        else
          ar[x] := x + n
      end )
  else
    // for x := 0 to 255 do
    TParallel.&For( 0, 255,
      procedure( x: integer )
      begin
        if integer( x - n ) < 0
        then
          ar[x] := 0
        else
          ar[x] := x - n
      end );

  for y := 0 to Bitmap.Height - 1 do
    begin

      Ziel := Bitmap.Scanline[y];
      Quelle := Original.Scanline[y];

      // for LIdx := 0 to Bitmap.Width - 1 do
      TParallel.&For( 0, Bitmap.Width - 1,
        procedure( LIdx: integer )
        begin

          if not IgnoreWhite or ( ( Quelle[LIdx].rgbtBlue <> 255 ) or ( Quelle[LIdx].rgbtGreen <> 255 ) or ( Ziel[LIdx].rgbtGreen <> 255 ) )
          then
            begin
              Ziel[LIdx].rgbtBlue := ar[Quelle[LIdx].rgbtBlue];
              Ziel[LIdx].rgbtRed := ar[Quelle[LIdx].rgbtRed];
              Ziel[LIdx].rgbtGreen := ar[Quelle[LIdx].rgbtGreen];
            end;

        end );

    end;
end;

end.

Sir Rufo 19. Nov 2014 19:04

AW: Tparallel und Bitmap-Bearbeitung
 
@Harry Stahl

Schau dir mal an, was Delphi-Referenz durchsuchenTBitmap.Scanline eigentlich macht, dann sieht man auch, warum das der Zugriff per Pixel so langsam ist und der Zugriff per Scanline schneller (aber eben nur dann, wenn man immer Zeile für Zeile abarbeitet). ;)

Wenn du das parallelsieren möchtest, dann musst du für jeden Task ein eigenes Bitmap zur Verfügung stellen.

Dejan Vu 19. Nov 2014 19:15

AW: Tparallel und Bitmap-Bearbeitung
 
Ist das Parallele jetzt so viel schneller? Da wird ja wieder nur ein Pixel pro Thread verarbeitet.. Bringt es das?

Harry Stahl 19. Nov 2014 19:36

AW: Tparallel und Bitmap-Bearbeitung
 
Also nach meinen bisherigen Erfahrungen und Tests ist Scanline immer der Flaschenhals, weil eben Scanline soviel Aufrufe selbst noch tätigt. Meine optimierten Routinen (dazu gehört das gezeigte Beispiel nicht) verwenden immer nur einen einzigen Aufruf von Scanline (um die Startposition zu erhalten), die restlichen Zugriffe finden aufgrund von Berechnungen statt.

Davon abgesehen greife ich hier ja gar nicht auf Pixel (canvas.pixels[x, y]) zu, sondern auf ^TRGBTriple, was letztlich ein Zeiger auf diese Struktur ist:

tagRGBTRIPLE = record
rgbtBlue: Byte;
rgbtGreen: Byte;
rgbtRed: Byte;
end;

Meinst Du, dass Scanline nicht Threadsafe ist? Das könnte ich ja umgehen, indem ich es so mache, wie oben angedeutet habe.

Edit: Habe gerade gesehen, dass Du (Sir Rufo) eine eigene Version entworfen hast. Super. Werde ich gleich mal testen...

Sir Rufo 19. Nov 2014 19:39

AW: Tparallel und Bitmap-Bearbeitung
 
Zitat:

Zitat von Dejan Vu (Beitrag 1280389)
Ist das Parallele jetzt so viel schneller? Da wird ja wieder nur ein Pixel pro Thread verarbeitet.. Bringt es das?

Nun mit der normalen Variante hat er bei einem Bild 2ms benötigt. Mit der parallelen war das schon nach 39ms vollbracht :mrgreen:

Es bringt also nur etwas, wenn man die Bilder in separate Einzelbilder unterteilt und dieses Teilbild dann in einem Task abarbeitet. Dabei sollte das Teilbild nicht zu klein sein, sonst macht es keinen Sinn.

Harry Stahl 19. Nov 2014 19:51

AW: Tparallel und Bitmap-Bearbeitung
 
Ja, in der Tat, hier dauert es nun ca. 570 MS, Beschleunigungsziel wird also nicht erreicht.

Aber auf jeden Fall hast Du schon mal mehr Licht ins Dunkel gebracht, also dafür schon mal vielen Dank. Werde weiter probieren und posten, wenn ich die Lösung habe...

Harry Stahl 20. Nov 2014 01:27

AW: Tparallel und Bitmap-Bearbeitung
 
Was seltsam ist: Der zuletzt von mir gepostete Code funktioniert ohne Fehler, wenn man vor Aufruf der beiden Tasks die folgenden beiden Zeilen einfügt:

Delphi-Quellcode:
QB := Original.ScanLine[0]; // <-- Die
ZB := Bitmap.ScanLine[0]; // <-- und die Zeile im Code, dann keine Access-Violation
Also eigentlich ziemlich sinnlos, aber durch den Aufruf von Scanline wird "TBitmap.GetScanline aufgerufen und irgendwas dort bewirkt, dass der Zugriff dann stabil ist:

Delphi-Quellcode:
function TBitmap.GetScanLine(Row: Integer): Pointer;
begin
  Changing(Self);
  with FImage.FDIB, dsbm, dsbmih do
  begin
    if (Row < 0) or (Row >= bmHeight) then
      InvalidOperation(@SScanLine);
    DIBNeeded;
    GDIFlush;
    if biHeight > 0 then // bottom-up DIB
      Row := biHeight - Row - 1;
    Result := PByte(bmBits) +
      Row * BytesPerScanline(biWidth, biBitCount, 32);
  end;
end;
Noch besser wird die Performance, wenn man bei einer 4-Core-cpu das Bild in 4 Tasks aufteilt und berechnen lässt.

Optimal wäre also, wenn man anhand der verfügbaren cpu-Kerne das Bild in eine entsprechende Anzahl Bereiche einteilt und dann die entsprechende Anzahl von Tasks dynamisch erzeugt. Da werde ich mich jetzt mal ran machen. Die Performance-Gewinne scheinen vielversprechend zu sein...

Harry Stahl 20. Nov 2014 02:05

AW: Tparallel und Bitmap-Bearbeitung
 
OK, so sieht das nun fertig aus, abhängig von der Anzahl der CPUs werden entsprechende Worker-Tasks erzeugt, die dann die einzelnen Bildteile berechnen. Auf einem 4-Kern-PC spürt man wirklich einen deutlichen Unterschied.

Hier die geänderten Teile:

Delphi-Quellcode:
var
  ...
Procedure CreateNewWorkerTask (var T: ITask; L:Integer);
  begin
    T := TTask.Create(procedure ()
    var
      Ziel, Quelle : ^TRGBTriple;
      x, y, Start, Stop: Integer; OK: Boolean;
      begin
        if L = 0 then Start := 0 else Start := L * (Bitmap.Height div cpus);
        if L = 0 then Stop := Bitmap.Height div cpus else Stop := (Bitmap.Height div cpus) * (L+1);

        if Stop > Bitmap.Height-1 then Stop := Bitmap.Height-1;

        for y := Start to Stop do begin
          Ziel  := Bitmap.Scanline[y];
          Quelle := Original.Scanline[y];

          for x := 0 to (Bitmap.Width-1) do begin
            if IgnoreWhite then begin
              OK := (Quelle^.rgbtBlue <> 255) or (Quelle^.rgbtGreen <> 255) or (Quelle^.rgbtred <> 255);
            end else begin
              OK := True;
            end;

            if OK then begin
              Ziel^.rgbtBlue := ar[Quelle^.rgbtBlue];
              Ziel^.rgbtred  := ar[Quelle^.rgbtred];
              Ziel^.rgbtGreen := ar[Quelle^.rgbtGreen];
            end;

            inc(Ziel);
            inc(Quelle);
          end;
        end;
      end
      );
  end;

begin
...

    QB := Original.ScanLine[0]; // <-- Die
    ZB := Bitmap.ScanLine[0]; // <-- und die Zeile im Code, dann keine Access-Violation

    cpus := GetCPUCount div GetCPULogicalProcessorCount; // Hier in Delphi-Praxis gefunden

    SetLength(myTasks, cpus);

    for L := 0 to cpus-1 do begin
      CreateNewWorkerTask (myTasks[L], L);
      myTasks[L].Start;
    end;

    TTask.WaitForAll(myTasks);

Harry Stahl 20. Nov 2014 22:23

AW: Tparallel und Bitmap-Bearbeitung
 
Also, um das noch abzuschließen: Offensichtlich ist Scanline nicht threadsafe. Daher habe ich Scanline nur einmal außerhalb des Task-Threads verwendet und innerhalb des Tasks greife ich berechnet auf den DIB-Speicher zu.

Auf einer 6-Kern CPU wird das Bild dann mit ca. 40 MS bearbeitet, also mit Parallel-Bearbeitung 4 mal schneller als ohne.

Hier die fertige Lösung für 24 und 32-Bit-Bitmap (32-Bit wird noch ca. 20% schneller berechnet).

Delphi-Quellcode:
// Bitmap = Zielbitmap; Original = Quelle, Value zwischen -240 und + 240
procedure Helligkeit(Bitmap: TBitmap; Const Original : TBitmap; Value: integer; IgnoreWhite: Boolean);
var
  L,xx,LL,UsedCPUs : integer; n : byte;
  ar : array[0..255] of byte;
  myTasks: array of ITask;
  Dest, Src: Pointer;

  Procedure CreateNewWorkerTask24 (var T: ITask; L:Integer);
  begin
    T := TTask.Create(procedure ()
    var
      Ziel, Quelle : ^TRGBTriple;
      x, y, Start, Stop: Integer;
    begin
      if Bitmap.Height = 1 then begin
        Start := 0; Stop := 0;
      end else begin
        if L = 0 then Start := 0 else Start := (L * (Bitmap.Height div UsedCpus)) + 1;
        if L = 0 then Stop := Bitmap.Height div UsedCpus else Stop := (Bitmap.Height div UsedCpus) * (L+1);
        if Stop > Bitmap.Height-1 then Stop := Bitmap.Height-1;
      end;

      for y := Start to Stop do begin
        Ziel := Pointer(Integer(Dest) + LL * Y);
        Quelle := Pointer(Integer(Src) + LL * Y);

        for x := 0 to (Bitmap.Width-1) do begin
          if (IgnoreWhite = false) or (Quelle^.rgbtBlue <> 255) or (Quelle^.rgbtGreen <> 255) or (Quelle^.rgbtred <> 255) then begin
            Ziel^.rgbtBlue := ar[Quelle^.rgbtBlue];
            Ziel^.rgbtred  := ar[Quelle^.rgbtred];
            Ziel^.rgbtGreen := ar[Quelle^.rgbtGreen];
          end;

          inc(Ziel);
          inc(Quelle);
        end;
      end;
    end
    );
  end;

  Procedure CreateNewWorkerTask32 (var T: ITask; L:Integer);
  begin
    T := TTask.Create(procedure ()
    var
      RGBAQuelle, RGBAZiel: pRGBALine;
      x, y, Start, Stop: Integer;
    begin
      if Bitmap.Height = 1 then begin
        Start := 0; Stop := 0;
      end else begin
        if L = 0 then Start := 0 else Start := (L * (Bitmap.Height div UsedCpus)) + 1;
        if L = 0 then Stop := Bitmap.Height div UsedCpus else Stop := (Bitmap.Height div UsedCpus) * (L+1);
        if Stop > Bitmap.Height-1 then Stop := Bitmap.Height-1;
      end;

      for y := Start to Stop do begin
        RGBAZiel := Pointer(Integer(Dest) + LL * Y); //Bitmap.ScanLine[y];
        RGBAQuelle := Pointer(Integer(Src) + LL * Y); //Original.Scanline[y];

        for x := 0 to (Bitmap.Width-1) do begin
          if RGBAZiel^[x].rgbReserved <> 0 then begin
            if (IgnoreWhite = false) or (RGBAQuelle^[x].rgbBlue <> 255) or (RGBAQuelle^[x].rgbgreen <> 255) or (RGBAQuelle^[x].rgbred <> 255) then begin
              RGBAZiel^[x].rgbBlue := ar[RGBAQuelle^[x].rgbBlue];
              RGBAZiel^[x].rgbred := ar[RGBAQuelle^[x].rgbred];
              RGBAZiel^[x].rgbgreen := ar[RGBAQuelle^[x].rgbgreen];
            end;
          end;
        end;
      end;
    end
    );
  end;

begin
  n := abs(value);

  //Fall berücksichtigen, dass Bitmap nur 1 Zeile hoch oder weniger Zeilen als CPUS an Board
  if Bitmap.Height < CPUsOnBoard then UsedCPUs := Bitmap.height else UsedCPUs := CPUsOnBoard;

  if value > 0 then begin
    for xx := 0 to 255 do if integer(xx + n) > 255 then ar[xx] := 255 else ar[xx] := xx + n
  end else begin
    for xx := 0 to 255 do if integer(xx - n) < 0 then ar[xx] := 0 else ar[xx] := xx - n;
  end;

  Dest := Bitmap.ScanLine[0];
  Src := Original.ScanLine[0];

  if Bitmap.Height = 1 then begin
    LL := 0;
  end else begin
    LL := Integer(Bitmap.ScanLine[1]) - Integer(Dest);
  end;

  SetLength(myTasks, UsedCpus);

  for L := 0 to UsedCpus-1 do begin
    if Bitmap.pixelformat = pf32bit then begin
      CreateNewWorkerTask32 (myTasks[L], L);
    end else begin
      CreateNewWorkerTask24 (myTasks[L], L);
    end;
    myTasks[L].Start;
  end;

  TTask.WaitForAll(myTasks);
end;
Wenn ich das richtig verstanden habe, muss man hinterher nicht "aufräumen"? Ein Free für den Task gibt es nicht.

Sir Rufo 20. Nov 2014 23:07

AW: Tparallel und Bitmap-Bearbeitung
 
Nun ja, warum sollte das auch threadsafe ausgelegt sein? Das ist Bestandteil der VCL und die ist eben auf einen Thread ausgelegt.

Und bei einem
Delphi-Quellcode:
interface
gehe ich erstmal davon aus, dass sich das von selber entfernt (ja, es gibt Ausnahmen) und macht gerade bei den Tasks auch Sinn.

Noch was zu den Tasks:
  • Die 24 und 32 Varianten kann man bestimmt zusammenfassen
  • Du kannst dir einen eigenen ThreadPool erzeugen und dort Min/Max der Workerthreads festlegen auf die Anzahl der CPU-Kerne. Wenn du dann die Tasks übergibst, dann stehen auch sofort diese Threads zur Verfügung und arbeiten die Tasks direkt ab. Ansonsten wartet der ThreadPool und analysiert das System, ob denn noch ein weiterer Thread gestartet werden könnte. Ist der Thread einmal erzeugt, dann bleibt der bis zum Ende des ThreadPools.

Harry Stahl 20. Nov 2014 23:48

AW: Tparallel und Bitmap-Bearbeitung
 
Zitat:

Zitat von Sir Rufo (Beitrag 1280551)
Noch was zu den Tasks:
  • Die 24 und 32 Varianten kann man bestimmt zusammenfassen
  • Du kannst dir einen eigenen ThreadPool erzeugen und dort Min/Max der Workerthreads festlegen auf die Anzahl der CPU-Kerne. Wenn du dann die Tasks übergibst, dann stehen auch sofort diese Threads zur Verfügung und arbeiten die Tasks direkt ab. Ansonsten wartet der ThreadPool und analysiert das System, ob denn noch ein weiterer Thread gestartet werden könnte. Ist der Thread einmal erzeugt, dann bleibt der bis zum Ende des ThreadPools.

Zusammenfassen der Bit-Varianten wäre zwar möglich, aber bei 24-Bit Bitmaps mit Performance-Verlust verbunden. Bei einer 32-Bitmap kann das System die 4*8 Byte gut in einem Rutsch in die Register einer 32-Bit oder 64-Bit CPU laden. Daher ist dann der Zugriff per Array in den Speicherbereich die effizienteste Lösung. Bei der 24-Bit-Bitmap käme das wegen der nicht passenden Registerbreite nicht so gut hin, daher ist es dort schneller, per Pointer-Addition auf den Speicherbereich zu zeigen und jeweils das entsprechende Byte direkt zu bearbeiten.

Oder gibt es da noch eine zusammenfassende Lösung, die ich noch nicht kenne?

Das mit den ThreadPools werde ich mir mal ansehen. Ich denke, die Parallel-Unit werde ich mir nach und nach erschließen, denn das alles ist sehr vielversprechend und ja auch viel leichter zu handhaben als die Verwendung eines Threads (also zumindest, was die Erzeugung eines Tasks im Vergleich mit einem Thread betrifft).

Sir Rufo 21. Nov 2014 00:20

AW: Tparallel und Bitmap-Bearbeitung
 
Nun ja, man konnte ja seit geraumer Zeit schon einen anonymen Thread erstellen, allerdings ist das etwas völlig anderes als das mit den Tasks.

Dort macht man jetzt genau das, was man eigentlich möchte: Eine Aufgabe erstellen, die in einem anderen Thread-Kontext ausgeführt werden soll :)


Alle Zeitangaben in WEZ +1. Es ist jetzt 22:28 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 by Thomas Breitkreuz