Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen (https://www.delphipraxis.net/210190-abschlussprojekt-fiae-optimierung-von-algorithmen-vergleich-von-polygonen.html)

Gyrospeter 15. Mär 2022 16:15


Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Guten Abend werte Delphi-Gemeinde,

nun ist es für mich auch soweit, ich befinde mich gerade in den Vorbereitungen meines Abschlussprojektes und dabei geht es um Umgestaltung bzw. das effizienter Gestalten eines schon vorhandenen bzw. schon vorhandener Algorithmen.
Hierbei wird Wert auf die Laufzeit-, Speichereffizienz und kognitive Effizienz als drei Hauptkriterien gelegt.
Vorab, die Algorithmen die verbessert werden sollen hinlänglich der Effizienz, habe ich vor längerer Zeit selbst geschrieben.
In der Praxis hat sich herausgestellt, dass diese Algorithmen leider sehr langsam arbeiten und bei großen Listen auch ziemlich viel Speicher fressen.
Problem dabei ist, dass ich auf den ersten Blick nicht wirklich erkennen kann, wo Verbesserungspotential ist (außer z.B. auf die Listen als Feldvariablen zu verzichten und diese eben lokal zu erstellen und wieder wegzuräumen).
Ich möchte hier keine 1:1 Lösung angeboten bekommen, aber einige Tipps wo ich was verbessern könnte bzw. wo welche Stellen ich mir noch anschauen sollte, bei denen Verbesserungspotential herrscht :)

Kurzum was mein Code macht:
Es gibt 2 generische Listen die aus Records bestehen, vielleicht wären hier sogar generische Listen aus Vektoren besser :?: diese Listen beinhalten Vektoren (Koordinaten) aus denen sich dann ein Polygon zeichnen lässt. Diese Polygone sollen nun verglichen werden. Es gibt Fälle in denen der Startpunkt bei Polygon 1 der Endpunkt bei Polygon 2 und umgekehrt ist, dann sind die Polygone dennoch gleich. Nur eben in der Liste von den umgedreht, diese wird dann durch eine Funktion rotiert. Weiterhin gibt es die Möglichkeit geschlossene oder offene Polygone als Parameter in die Listen zu übergeben. Auch auf doppelte Einträge wird geachtet und diese aus der Liste entfernt.

Als einen guten Lösungsansatz zur Vorbereitung fand ich:
https://stackoverflow.com/questions/...lygons-is-same

Und hier der Code:
Delphi-Quellcode:
type
  TCadVec3 = record
    x: Integer;
    y: Integer;
    z: Integer;
  end;

  TCadVec3List = TList<TCadVec3>;

  TCADVecListHelperMainFrm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private-Deklarationen }
    FFirstCADList: TCadVec3List;
    FSecondCADList: TCadVec3List;
  public
    { Public-Deklarationen }
  end;

var
  CADVecListHelperMainFrm: TCADVecListHelperMainFrm;

//  Globale Methoden.
  procedure RotateList(AList: TCadVec3List; AIndex: Integer);
  procedure RemoveIdenticalFromList(AList: TCadVec3List; AOpen: Boolean);

  function IsListItemEqual(AFirstRecord, ASecondRecord: TCadVec3): Boolean;
  function CompareOpenPolygons(AFirstList, ASecondList: TCadVec3List; ADirectional: Boolean): Boolean;
  function CompareClosedPolygon(AFirstList, ASecondList: TCadVec3List; AStartIdx: Integer; ADirectional: Boolean): Boolean;
  function CompareBothLists(AFirstList, ASecondList: TCadVec3List; ADirectional, AFirstListOpen, ASecondListOpen: Boolean): Boolean;

implementation


{$R *.dfm}

function CompareBothLists(AFirstList,
  ASecondList: TCadVec3List; ADirectional, AFirstListOpen, ASecondListOpen: Boolean): Boolean;
var
  i: Integer;
  LFirstTmpList,LSecondTmpList: TCadVec3List;
begin
//  Vergleich der Listen bzw. der Polygone und ob diese geometrisch gleich, oder ungleich sind.
//  Hierbei werden verschiedene Rahmenbedingungen und Szenarien aufgeführt -> geschlossene Polygone, offene Polygone, geschlossenes/offenes Polygon.
  LFirstTmpList := TCadVec3List.Create;
  LSecondTmpList := TCadVec3List.Create;

  try
    Result := False;

    if (AFirstList = nil) or (ASecondList = nil) then
      Exit;

    LFirstTmpList.AddRange(AFirstList);
    LSecondTmpList.AddRange(ASecondList);
    RemoveIdenticalFromList(LFirstTmpList, AFirstListOpen);
    RemoveIdenticalFromList(LSecondTmpList, ASecondListOpen);

    if (AFirstListOpen = ASecondListOpen) and (LFirstTmpList.Count <> LSecondTmpList.Count) then
      Exit;

    if not AFirstListOpen and not ASecondListOpen then
    begin
      for i := 0 to LSecondTmpList.Count - 1 do
      begin
        if IsListItemEqual(LFirstTmpList[0], LSecondTmpList[i]) then
        begin
          Result := CompareClosedPolygon(LFirstTmpList, LSecondTmpList, i, ADirectional);

          if Result then
            Break;
        end;
      end;
      Exit;
    end
    else if AFirstListOpen and ASecondListOpen then
      Result := CompareOpenPolygons(LFirstTmpList, LSecondTmpList, ADirectional)
    else if AFirstListOpen and not ASecondListOpen then
    begin
      if IsListItemEqual(LFirstTmpList.First, LFirstTmpList.Last) then
      begin
        LFirstTmpList.Delete(LFirstTmpList.Count - 1);

        for i := 0 to LSecondTmpList.Count - 1 do
        begin
          if IsListItemEqual(LFirstTmpList[0], LSecondTmpList[i]) then
          begin
            Result := CompareClosedPolygon(LFirstTmpList, LSecondTmpList, i, ADirectional);

            if Result then
              Break;
          end;
        end;
        Exit;
      end
      else
        Exit;
    end
    else
    begin
      if IsListItemEqual(LSecondTmpList.First, LSecondTmpList.Last) then
      begin
        LSecondTmpList.Delete(LSecondTmpList.Count - 1);

        for i := 0 to LFirstTmpList.Count - 1 do
        begin
          if IsListItemEqual(LSecondTmpList[0], LFirstTmpList[i]) then
          begin
            Result := CompareClosedPolygon(LSecondTmpList, LFirstTmpList, i, ADirectional);

            if Result then
              Break;
          end;
        end;
        Exit;
      end
      else
        Exit;
    end;
  finally
    LSecondTmpList.Free;
    LFirstTmpList.Free;
  end;
end;

function CompareClosedPolygon(AFirstList, ASecondList: TCadVec3List; AStartIdx: Integer; ADirectional: Boolean): Boolean;
var
  i: Integer;
  LEqual: Boolean;
  LTmpList: TCadVec3List;
begin
//  Vergleich von geschlossenen Polygonen bzw. einem offenen und einem geschlossenen Polygon.
  LTmpList := TCadVec3List.Create;

  try
    LEqual := True;
    LTmpList.AddRange(ASecondList);
    RotateList(LTmpList, AStartIdx);

    for i := 0 to AFirstList.Count - 1 do
    begin
      if not IsListItemEqual(AFirstList[i], LTmpList[i]) then
      begin
        LEqual := False;
        Break;
      end;
    end;

    if not LEqual and not ADirectional then
    begin
      LEqual := True;
      LTmpList.Clear;
      LTmpList.AddRange(ASecondList);
      LTmpList.Reverse;
      RotateList(LTmpList, (LTmpList.Count - 1) - AStartIdx);

      for i := 0  to AFirstList.Count - 1 do
      begin
        if not IsListItemEqual(AFirstList[i], LTmpList[i]) then
        begin
          LEqual := False;
          Break;
        end;
      end;
    end;
    Result := LEqual;
  finally
    LTmpList.Free;
  end;
end;

function CompareOpenPolygons(AFirstList, ASecondList: TCadVec3List;
  ADirectional: Boolean): Boolean;
var
  i: Integer;
  LEqual: Boolean;
  LTmpList: TCadVec3List;
begin
//  Vegleich von zwei offenen Polygonen.
  LTmpList := TCadVec3List.Create;

  try
    LEqual := True;
    LTmpList.AddRange(ASecondList);

    for i := 0 to AFirstList.Count - 1 do
    begin
      if not IsListItemEqual(AFirstList.Items[i], LTmpList.Items[i]) then
      begin
        LEqual := False;
        Break;
      end;
    end;

    if not LEqual and not ADirectional then
    begin
      LEqual := True;
      LTmpList.Clear;
      LTmpList.AddRange(ASecondList);
      LTmpList.Reverse;

      for i := 0 to AFirstList.Count - 1 do
      begin
        if not IsListItemEqual(AFirstList.Items[i], LTmpList.Items[i]) then
        begin
          LEqual := False;
          Break;
        end;
      end;
    end;
    Result := LEqual;
  finally
    LTmpList.Free;
  end;
end;

procedure TCADVecListHelperMainFrm.FormCreate(Sender: TObject);
begin
//  Instanzen erzeugen.
  FFirstCADList := TList<TCadVec3>.Create;
  FSecondCADList := TList<TCadVec3>.Create;
end;

procedure TCADVecListHelperMainFrm.FormDestroy(Sender: TObject);
begin
//  Instanzen freigeben.
  FSecondCADList.Free;
  FFirstCADList.Free;
end;

procedure RotateList(AList: TCadVec3List; AIndex: Integer);
var
  i: Integer;
begin
//  Rotation der Liste zum vorgegebenen Startindex bzw. Startpunkt.
  if (AList = nil) or (AIndex < 0) or (AIndex > AList.Count - 1) then
    Exit;

  for i := 0 to AIndex - 1 do
  begin
    AList.Move(0, AList.Count - 1);
  end;
end;

function IsListItemEqual(AFirstRecord, ASecondRecord: TCadVec3): Boolean;
begin
//  Vergleich der Elemente in der Liste.
  if (AFirstRecord.x = ASecondRecord.x) and (AFirstRecord.y = ASecondRecord.y) and (AFirstRecord.z = ASecondRecord.z) then
    Result := True
  else
    Result := False;
end;

procedure RemoveIdenticalFromList(AList: TCadVec3List; AOpen: Boolean);
var
  i: Integer;
  LCadActualItem,LCadNextItem: TCadVec3;
begin
//  Entferne doppelte, aufeinander Folgende Einträge bzw. Elemente.
  for i := AList.Count - 1 downto 1 do
  begin
    LCadActualItem := AList.Items[i];
    LCadNextItem := AList.Items[i - 1];

    if IsListItemEqual(LCadActualItem, LCadNextItem) then
      AList.Delete(i - 1);
  end;

  if not AOpen then
  begin
    if IsListItemEqual(AList.First, AList.Last) then
      AList.Delete(AList.Count - 1);
  end;
  AList.TrimExcess;
end;

TiGü 16. Mär 2022 08:51

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Ein paar Testdaten zum schnellen Ausprobieren wäre eine gute Idee, damit wir leicht die Fälle erkennen können, woran es genau scheitert.

Beim ersten drüber schauen lässt sich relativ leicht zum Beispiel das hier optimieren:

Delphi-Quellcode:
function IsListItemEqual(AFirstRecord, ASecondRecord: TCadVec3): Boolean;
begin
// Vergleich der Elemente in der Liste.
  if (AFirstRecord.x = ASecondRecord.x) and (AFirstRecord.y = ASecondRecord.y) and (AFirstRecord.z = ASecondRecord.z) then
    Result := True
  else
    Result := False;
end;
Nach:

Delphi-Quellcode:
function IsListItemEqual(const AFirstRecord, ASecondRecord: TCadVec3): Boolean;
begin
  // Vergleich der Elemente in der Liste.
  Result := (AFirstRecord.x = ASecondRecord.x) and (AFirstRecord.y = ASecondRecord.y) and (AFirstRecord.z = ASecondRecord.z);
end;
Durch die Verwendung von const in den Parametern werden beide Records nicht mehr kopiert (je nach verwendeten Compiler) und die Zuweisung vom Result lässt sich auch kürzen.
Ergibt in Debug Win32 auch sieben Zeilen weniger generierten Assembler (38 zu 31 Zeilen).

freimatz 16. Mär 2022 09:43

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Wie gross sind die Listen? Wie oft kommen die Equal vor?

TiGü 16. Mär 2022 09:52

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Wenn ich mich jetzt nicht verguckt habe, kann man die Hauptfunktion auch etwas verkürzen zu:

Delphi-Quellcode:
function CompareBothLists(AFirstList,
  ASecondList: TCadVec3List; ADirectional, AFirstListOpen, ASecondListOpen: Boolean): Boolean;
var
  LFirstTmpList,LSecondTmpList: TCadVec3List;

  function PleaseChangeToAMeaningfulName(const AList1, AList2: TCadVec3List; const ADirectional: Boolean): Boolean;
  var
      i: Integer;
  begin
    Result := False;
    for i := 0 to AList2.Count - 1 do
    begin
      if IsListItemEqual(AList1[0], AList2[i]) then
      begin
        Result := CompareClosedPolygon(AList1, AList2, i, ADirectional);

        if Result then
          Break;
      end;
    end;
  end;

  function PleaseChangeToAMeaningfulName2(const AList: TCadVec3List): Boolean;
  begin
    Result := IsListItemEqual(AList.First, AList.Last);

    if Result then
    begin
      AList.Delete(AList.Count - 1);
    end;
  end;

begin
// Vergleich der Listen bzw. der Polygone und ob diese geometrisch gleich, oder ungleich sind.
// Hierbei werden verschiedene Rahmenbedingungen und Szenarien aufgeführt -> geschlossene Polygone, offene Polygone, geschlossenes/offenes Polygon.
  LFirstTmpList := TCadVec3List.Create;
  LSecondTmpList := TCadVec3List.Create;

  try
    Result := False;

    if (AFirstList = nil) or (ASecondList = nil) then
      Exit;

    LFirstTmpList.AddRange(AFirstList);
    LSecondTmpList.AddRange(ASecondList);
    RemoveIdenticalFromList(LFirstTmpList, AFirstListOpen);
    RemoveIdenticalFromList(LSecondTmpList, ASecondListOpen);

    if (AFirstListOpen = ASecondListOpen) and (LFirstTmpList.Count <> LSecondTmpList.Count) then
      Exit;

    if not AFirstListOpen and not ASecondListOpen then
    begin
      Result := PleaseChangeToAMeaningfulName(LFirstTmpList, LSecondTmpList, ADirectional);
    end
    else if AFirstListOpen and ASecondListOpen then
    begin
      Result := CompareOpenPolygons(LFirstTmpList, LSecondTmpList, ADirectional)
    end
    else if AFirstListOpen and not ASecondListOpen then
    begin
      if PleaseChangeToAMeaningfulName2(LFirstTmpList) then
      begin
        Result := PleaseChangeToAMeaningfulName(LFirstTmpList, LSecondTmpList, ADirectional);
      end;
    end
    else
    begin
      if PleaseChangeToAMeaningfulName2(LSecondTmpList) then
      begin
        Result := PleaseChangeToAMeaningfulName(LSecondTmpList, LFirstTmpList, ADirectional);
      end;
    end;
  finally
    LSecondTmpList.Free;
    LFirstTmpList.Free;
  end;
end;
Ob jetzt Subroutinen oder eigene freistehende Funktionen ist Geschmackssache.

TiGü 16. Mär 2022 10:15

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Bei RemoveIdenticalFromList musst du anhand deiner Testdaten prüfen, wie oft es wirklich vorkommt, dass wirklich doppelte und aufeinanderfolgende Einträge existieren.

Durch das Delete wird TListHelper.InternalDoDeleteN aufgerufen und das sorgt durch den Move Befehl für teure Speicheroperationen.
Vielleicht wäre hier ein Ansatz mit temporärer Liste mit vor initialisierter Länge besser, in der du nur die Elemente packst, die nicht doppelt sind.

Gyrospeter 16. Mär 2022 10:40

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Zitat:

Zitat von TiGü (Beitrag 1503383)
Ein paar Testdaten zum schnellen Ausprobieren wäre eine gute Idee, damit wir leicht die Fälle erkennen können, woran es genau scheitert.

Da habe ich ganz stumpf Integer als kleine Testung hart in die Listen eingetragen und je nachdem einen bis mehrere Werte geändert damit sich die Polygone unterscheiden:

Delphi-Quellcode:
procedure TCADVecListHelperMainFrm.FillBothLists;
var
  LRecord,ChangedRec: TCadVec3;
  i: Integer;
begin
//  Fülle beide Listen mit random default Werten.

  if (FFirstCADList <> nil) and (FSecondCADList <> nil) and (FFirstCADList.Count = 0) and (FSecondCADList.Count = 0) then
  begin
    for i := 0 to 1000 - 1 do
    begin
      LRecord.x := RandomRange(0, 1000);
      LRecord.y := RandomRange(0, 1000);
      LRecord.z := RandomRange(0, 1000);
      FFirstCADList.Add(LRecord);
    end;

    ChangedRec.x := 100;
    ChangedRec.y := 100;
    ChangedRec.z := 100;
    FSecondCADList.AddRange(FFirstCADList);
//    FSecondCADList.Items[12] := ChangedRec;
  end;
end;

Gyrospeter 16. Mär 2022 10:42

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Zitat:

Zitat von freimatz (Beitrag 1503390)
Wie gross sind die Listen? Wie oft kommen die Equal vor?

Listen sind unterschiedlich groß, je nach Komplexität des Polygons. Kann aus 5 Einträgen bestehen, kann aber auch aus 5000 Einträgen bestehen. Siehe mein Code-Beispiel darüber, bei denen ich einfach testweise die Listen befüllt habe.

freimatz 16. Mär 2022 10:44

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Danke für Deine Ausführungen Gyrospeter. Genau deshalb fragte ich nach. Kommt das nur selten vor ist ein Delete direkt besser, wenn es mehr als nur selten vorkommt dann ist etwas anders besser. Man kann einen temporären Container nehmen oder zuerst nur mal die Einträge als zu löschen markieren und danach in einem Rutsch zu löschen.
Statt einer Liste wäre vermutlich auch Arrays performater.
Aber! Das ist alles Stochern im Nebel. Ein Profiling wäre richtig und wichtig.

Gyrospeter 16. Mär 2022 10:49

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Zitat:

Zitat von TiGü (Beitrag 1503402)
Bei RemoveIdenticalFromList musst du anhand deiner Testdaten prüfen, wie oft es wirklich vorkommt, dass wirklich doppelte und aufeinanderfolgende Einträge existieren.

Durch das Delete wird TListHelper.InternalDoDeleteN aufgerufen und das sorgt durch den Move Befehl für teure Speicheroperationen.
Vielleicht wäre hier ein Ansatz mit temporärer Liste mit vor initialisierter Länge besser, in der du nur die Elemente packst, die nicht doppelt sind.

Das ist eine gute Idee, müsste also dementsprechend nur jedes Element einzeln in die neue Liste packen und die Capacity der temporären Liste vordefinieren. Wie weiß ich denn dennoch, wie viele Elemente ich da reinpacken muss? Oder muss ich die Capacity nach jedem neu hinzugefügten Element um 1 erweitern?

Gyrospeter 16. Mär 2022 10:54

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Zitat:

Zitat von freimatz (Beitrag 1503411)
Ein Profiling wäre richtig und wichtig.

Ist mit dem Profiling das Testen im Bezug auf die Effizienz gemeint?
Leider habe ich ja z.B. bei der Laufzeiteffizienz noch keine richtige Referenz, da ich ja noch keinen Vergleich zwischen altem Code und den neuen, optimierteren Code habe :D :stupid:

TiGü 16. Mär 2022 11:42

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Zitat:

Zitat von Gyrospeter (Beitrag 1503414)
Zitat:

Zitat von TiGü (Beitrag 1503402)
Bei RemoveIdenticalFromList musst du anhand deiner Testdaten prüfen, wie oft es wirklich vorkommt, dass wirklich doppelte und aufeinanderfolgende Einträge existieren.

Durch das Delete wird TListHelper.InternalDoDeleteN aufgerufen und das sorgt durch den Move Befehl für teure Speicheroperationen.
Vielleicht wäre hier ein Ansatz mit temporärer Liste mit vor initialisierter Länge besser, in der du nur die Elemente packst, die nicht doppelt sind.

Das ist eine gute Idee, müsste also dementsprechend nur jedes Element einzeln in die neue Liste packen und die Capacity der temporären Liste vordefinieren. Wie weiß ich denn dennoch, wie viele Elemente ich da reinpacken muss? Oder muss ich die Capacity nach jedem neu hinzugefügten Element um 1 erweitern?

Die Capacity der temporären Liste würde ich so wählen, dass ohne große Neuallokation von Speicher der Inhalt der zu überprüfenden Liste reinpasst.
Also NewCapacity := Round(1.5 * OldList.Count) so als Startwert, man kann sich rantasten.
Damit ist der Platz schonmal vor reserviert und es kann ja durchaus vorkommen, dass keine Doppelungen vorkommen.
Immer wenn not IsListItemEqual dann wahr wird, kann der aktuelle Eintrag hinzugefügt werden.

freimatz 16. Mär 2022 12:12

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Hoi,
gemeint ist das.
Gemeint ist also das Messen der Zeiten in Deinen alten Code um herauszufinden wo es klemmt.

TurboMagic 16. Mär 2022 12:20

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
In manchen Delphi Versionen war eine Lite Version von AQTime dabei, es gibt aber auch andere Profiler.
Hat man ein neu genuges Delphi findet man glaube ich auch einen in GetIt, was ja aber in XE2 noch nicht
vorhanden ist, falls das wirklich die von dir genutzte Version ist.

Natürlich haben neuere Delphi versionen auch die eine oder andere Performance Verbesserung mit drin...

Gyrospeter 16. Mär 2022 12:32

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Zitat:

Zitat von freimatz (Beitrag 1503426)
Hoi,
gemeint ist das.
Gemeint ist also das Messen der Zeiten in Deinen alten Code um herauszufinden wo es klemmt.

Vielen Dank. Jetzt bin ich wieder etwas schlauer geworden =)

Zitat:

Zitat von TurboMagic (Beitrag 1503428)
In manchen Delphi Versionen war eine Lite Version von AQTime dabei, es gibt aber auch andere Profiler.
Hat man ein neu genuges Delphi findet man glaube ich auch einen in GetIt, was ja aber in XE2 noch nicht
vorhanden ist, falls das wirklich die von dir genutzte Version ist.

Natürlich haben neuere Delphi versionen auch die eine oder andere Performance Verbesserung mit drin...

Hab meine Delphi-Version korrigiert. Bei GetIt gibt es tatsächlich einen Profiler (AQTime). Muss ich beim hinzufügen etwas beachten oder gibt es ein gutes Manual über die Nutzung darüber?

Wäre den Threading einen Blick wert, z.B. beim Hinzufügen nicht doppelter Elemente zu den temporären Listen, also um das befüllen beider temporärer Listen zu parallelisieren oder macht es bei nur zwei Listen keinen Sinn?

Gyrospeter 16. Mär 2022 13:08

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Zitat:

Zitat von TiGü (Beitrag 1503422)
Die Capacity der temporären Liste würde ich so wählen, dass ohne große Neuallokation von Speicher der Inhalt der zu überprüfenden Liste reinpasst.
Also NewCapacity := Round(1.5 * OldList.Count) so als Startwert, man kann sich rantasten.
Damit ist der Platz schonmal vor reserviert und es kann ja durchaus vorkommen, dass keine Doppelungen vorkommen.
Immer wenn not IsListItemEqual dann wahr wird, kann der aktuelle Eintrag hinzugefügt werden.

Reicht es nicht einfach die NewCapacity einfach auf OldList.Count zu setzen, immerhin wird die neue Liste nie größer werden als die alte Liste, nur kleiner?
Deshalb zu Beginn mit NewCapacity := OldList.Count allokieren und nach dem Hinzufügen ein TrimExcess?
Oder sollte die Allokation besser blockweise (256, 512, 1024) geschehen?

Stevie 16. Mär 2022 13:16

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Ein paar Anmerkungen:
Es benötigt ein bisschen mehr als den oben von dir skizzierten Test, denn diese Testdaten verursachen nicht die durchaus teuren Operationen in deinem Code (z.B. Liste rotieren)
Generell sei gesagt, dass es nur schneller werden kann, wenn du auf die temporäre Liste verzichtest und sowohl identische Vektoren als auch unterschiedliche Startpositionen direkt abhandelst.

Bei Code, der auf jeden Fall garantiert, dass du auf gültige List Indizes zugreifst (durch eine 0 to Count-1 Schleife gegeben), kann es durchaus einen kleinen Schub geben, wenn du AList.List[i] benutzt, dadurch wird der getter übergangen, der noch einen Index in range check durchführt und deshalb obwohl geinlined den Code etwas aufbläht.

IsListItemEqual auf jeden Fall als inline markieren (daran denken, diese Funktion vor die anderen zu setzen, da der Compiler nur dann wirklich inlined)


Zum Profilen würde ich dir SamplingProfiler für den Anfang empfehlen (funktioniert am besten mit map file also unter Projektoptionen->Compiler->Linking diese auf detailed setzen). Obwohl der Profiler in der Oberfläche die neusten Delphi Versionen nicht auflistet funktioniert er tadellos. Für Fortgeschrittene empfehle ich Intel VTune (oder uProf, sollte man eine AMD CPU haben, das ist aber nicht ganz so cool wie VTune), dafür muss man aber eine pdb Datei erzeugen, dafür benötigt man map2pdb.

Zum Benchmarken möchte ich benchmark empfehlen, damit kannst du sehr einfach verschiedene Tests bauen, ohne groß mit Stopwatch und Co zu messen.

Gyrospeter 17. Mär 2022 14:02

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Zitat:

Zitat von Stevie (Beitrag 1503439)
Ein paar Anmerkungen:
Es benötigt ein bisschen mehr als den oben von dir skizzierten Test, denn diese Testdaten verursachen nicht die durchaus teuren Operationen in deinem Code (z.B. Liste rotieren)
Generell sei gesagt, dass es nur schneller werden kann, wenn du auf die temporäre Liste verzichtest und sowohl identische Vektoren als auch unterschiedliche Startpositionen direkt abhandelst.

Bei Code, der auf jeden Fall garantiert, dass du auf gültige List Indizes zugreifst (durch eine 0 to Count-1 Schleife gegeben), kann es durchaus einen kleinen Schub geben, wenn du AList.List[i] benutzt, dadurch wird der getter übergangen, der noch einen Index in range check durchführt und deshalb obwohl geinlined den Code etwas aufbläht.

IsListItemEqual auf jeden Fall als inline markieren (daran denken, diese Funktion vor die anderen zu setzen, da der Compiler nur dann wirklich inlined)


Zum Profilen würde ich dir SamplingProfiler für den Anfang empfehlen (funktioniert am besten mit map file also unter Projektoptionen->Compiler->Linking diese auf detailed setzen). Obwohl der Profiler in der Oberfläche die neusten Delphi Versionen nicht auflistet funktioniert er tadellos. Für Fortgeschrittene empfehle ich Intel VTune (oder uProf, sollte man eine AMD CPU haben, das ist aber nicht ganz so cool wie VTune), dafür muss man aber eine pdb Datei erzeugen, dafür benötigt man map2pdb.

Zum Benchmarken möchte ich benchmark empfehlen, damit kannst du sehr einfach verschiedene Tests bauen, ohne groß mit Stopwatch und Co zu messen.

Erst einmal vielen Dank für die Tipps bzgl. des Profilings.
Den Programmen werde ich mich morgen bzw. das Wochenende an meinem privaten Rechner zuhause widmen.
Hat denn AQtime auch in der Standard-Version aus dem GetIt-Manager einfache Profiling-Strukturen wie
z.B. das Aufzeigen von Speicherressourcen, oder muss ich dafür die PRO-Version erwerben?
Oder ist gar der SamplingProfiler kostenlos? Sieht mir etwas altbacken aus, funktioniert der gar mit
Delphi 10.4?

Gausi 17. Mär 2022 15:02

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Ich würde hier nicht mit Profiling ansetzen, um irgendwo ein paar Takte zu sparen, sondern an dem Algorithmus an sich ...

Wenn ich nicht komplett danebenliege, würde ich an der (ziemlich teuren) RotateList-Funktion ansetzen - die kann doch so ziemlich ersatzlos weg!

Diese Rotation um den StartIndex wird doch nur benutzt, damit du in der Schleife ListA[i] mit ListB[i] vergleichen kannst. Warum lässt du das nicht weg, und vergleichst ListA[i] mit ListB[(StartIdx + i) mod ListB.Count]? Dann ersparst du dir sämtliches Umkopieren der Listen (nach dem "Aufräumen"), und dein Algorithmus dürfte um ein Vielfaches schneller werden, da du damit einen Faktor von O(n^2) aus der Laufzeit rausnimmst (solange dauert das Rotate nämlich im Mittel!)

Dazu musst du ggf. die beiden Polygondarstellungen auf "offen" normieren (dazu hats du ja die passenden Funktionen), damit Start/Endpunkt auf keinen Fall doppelt vorkommen.

Interessant wird da höchstens noch der Fall, wenn du Kantenzüge zulässt, die sich selbst kreuzen, und somit ggf. eine Koordinate mehr als einmal auftauchen darf. Aber dann wird das Problem ohnehin schwieriger, weil du an diesen Kreuzungen ggf. anders "abbiegen" kannst, was letztlich auf Graphisomorphie rausläuft. Und das ist dann so schwierig, dass man nichteinmal weiß, ob es NP-vollständig ist oder nicht. :stupid: (Wobei es für den Spezialfall "Graphen mit Eulerweg" auch eventuell effiziente Tests geben könnte, da bin ich jetzt aber überfragt).

Stevie 17. Mär 2022 15:56

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Zitat:

Zitat von Gyrospeter (Beitrag 1503537)
Oder ist gar der SamplingProfiler kostenlos?

Ja

Zitat:

Zitat von Gyrospeter (Beitrag 1503537)
funktioniert der gar mit Delphi 10.4?

Ja, das habe ich mit
Zitat:

Obwohl der Profiler in der Oberfläche die neusten Delphi Versionen nicht auflistet funktioniert er tadellos.
aussagen wollen.

Zitat:

Zitat von Gausi (Beitrag 1503543)
Wenn ich nicht komplett danebenliege, würde ich an der (ziemlich teuren) RotateList-Funktion ansetzen - die kann doch so ziemlich ersatzlos weg!

Zu diesem Schluss war ich in den ersten beiden Sätzen meiner Anmerkungen auch gekommen. ;)

Die Anmerkung zum Profiling war zur Unterstützung und Bestätigung, denn bei entsprechenden Testdaten (die ebend nicht die sind, die weiter oben im Thread gezeigt wurden) dürfte man ziemlich schnell und einfach sehen, dass das befüllen der temp Liste und das rotieren nen dicken Anteil der Laufzeit beansprucht. Nicht jeder sieht seinem Code gleich sein asymptotisches Verhalten an :)

Gausi 17. Mär 2022 18:43

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Zitat:

Zitat von Stevie (Beitrag 1503549)
Zu diesem Schluss war ich in den ersten beiden Sätzen meiner Anmerkungen auch gekommen. ;)

Ups, sorry, das habe ich anscheinend überlesen.

Aber vielleicht als Ergänzung: Das haut natürlich besonders stark in dieser Zeile des Originalcodes rein, wenn AStartIdx = 0 ist.
Delphi-Quellcode:
RotateList(LTmpList, (LTmpList.Count - 1) - AStartIdx);
Dann wird die Liste mit 1000 Einträgen 1000 mal im Speicher hin und her kopiert, bevor mit der Überprüfung auf Gleichheit der beiden Listen begonnen wird.

Das mit der Index-Rechnerei mag zwar aufwändiger erscheinen, ist es aber nicht. Schon alleine deswegen, weil bei ungleichen Polygonen es sehr schnell zu einem Abbruch kommen wird, während sich die Rotation grade erst warmgelaufen hat.

Blup 21. Mär 2022 13:30

AW: Abschlussprojekt FIAE (Optimierung von Algorithmen) -> Vergleich von Polygonen
 
Bitte für das Formular eine eigene Unit.
Damit kannst du dir eine Bibliothek schreiben, die wenger Abhängigkeiten hat.
Das erleichtert auch die Implementation von Testfällen.

Mal ein erster Ansatz:
Delphi-Quellcode:
unit CadVec3;

interface

uses
  Generics.Collections;

type
  TCadVec3 = record
    x: Integer;
    y: Integer;
    z: Integer;
  end;

  TCadVec3List = TList<TCadVec3>;

// Globale Methoden.
  procedure RemoveIdenticalFromList(AList: TCadVec3List; AOpen: Boolean);

  function IsListItemEqual(AItem1, AItem2: TCadVec3): Boolean;
  function IsListEqual(AList1, AList2: TCadVec3List; AOffset: Integer): Boolean;

  function CompareOpenPolygons(AFirstList, ASecondList: TCadVec3List): Boolean; overload;
  function CompareOpenPolygons(AFirstList, ASecondList: TCadVec3List; ADirectional: Boolean): Boolean; overload;
  function CompareClosedPolygon(AFirstList, ASecondList: TCadVec3List): Boolean; overload;
  function CompareClosedPolygon(AFirstList, ASecondList: TCadVec3List; ADirectional: Boolean): Boolean; overload;

  function CompareBothLists(AFirstList, ASecondList: TCadVec3List; ADirectional, AFirstListOpen, ASecondListOpen: Boolean): Boolean;

implementation


function CompareBothLists(AFirstList, ASecondList: TCadVec3List; ADirectional, AFirstListOpen, ASecondListOpen: Boolean): Boolean;
var
  LFirstTmpList, LSecondTmpList: TCadVec3List;
begin
// Vergleich der Listen bzw. der Polygone und ob diese geometrisch gleich, oder ungleich sind.
// Hierbei werden verschiedene Rahmenbedingungen und Szenarien aufgeführt -> geschlossene Polygone, offene Polygone, geschlossenes/offenes Polygon.
  if (AFirstList = nil) or (ASecondList = nil) then
    Exit(False);

  LFirstTmpList := TCadVec3List.Create;
  LSecondTmpList := TCadVec3List.Create;
  try
    LFirstTmpList.AddRange(AFirstList);
    LSecondTmpList.AddRange(ASecondList);
    RemoveIdenticalFromList(LFirstTmpList, AFirstListOpen);
    RemoveIdenticalFromList(LSecondTmpList, ASecondListOpen);

    if AFirstListOpen and ASecondListOpen then
    begin
      if LFirstTmpList.Count <> LSecondTmpList.Count then
        Exit(False);

      Result := CompareOpenPolygons(LFirstTmpList, LSecondTmpList, ADirectional)
    end
    else
    begin
      if AFirstListOpen then
      begin
        if not IsListItemEqual(LFirstTmpList.First, LFirstTmpList.Last) then
          Exit(False);

        LFirstTmpList.Delete(LFirstTmpList.Count - 1);
      end
      else if ASecondListOpen then
      begin
        if not IsListItemEqual(LSecondTmpList.First, LSecondTmpList.Last) then
          Exit(False);

        LSecondTmpList.Delete(LSecondTmpList.Count - 1);
      end
      else // if not AFirstListOpen and not ASecondListOpen then
      begin
        if LFirstTmpList.Count <> LSecondTmpList.Count then
          Exit(False);
      end;

      Result := CompareClosedPolygon(LFirstTmpList, LSecondTmpList, ADirectional);
    end;
  finally
    LSecondTmpList.Free;
    LFirstTmpList.Free;
  end;
end;

function CompareClosedPolygon(AFirstList, ASecondList: TCadVec3List): Boolean;
var
  i: Integer;
begin
  // Vergleich von geschlossenen Polygonen bzw. einem offenen und einem geschlossenen Polygon.
  for i := 0 to ASecondList.Count - 1 do
  begin
    if IsListItemEqual(AFirstList[0], ASecondList[i]) then
    begin
      if IsListEqual(AFirstList, ASecondList, i) then
        Exit(True);
    end;
  end;
  Result := False;
end;

function CompareClosedPolygon(AFirstList, ASecondList: TCadVec3List; ADirectional: Boolean): Boolean;
begin
  Result := CompareClosedPolygon(AFirstList, ASecondList);
  if (not Result) and ADirectional then
  begin
    ASecondList.Reverse;
    Result := CompareClosedPolygon(AFirstList, ASecondList);
    ASecondList.Reverse;
  end;
end;

function CompareOpenPolygons(AFirstList, ASecondList: TCadVec3List): Boolean;
begin
// Vegleich von zwei offenen Polygonen.
  Result := IsListEqual(AFirstList, ASecondList, 0);
end;

function CompareOpenPolygons(AFirstList, ASecondList: TCadVec3List;
  ADirectional: Boolean): Boolean;
begin
  Result := CompareOpenPolygons(AFirstList, ASecondList);
  if (not Result) and ADirectional then
  begin
    ASecondList.Reverse;
    Result := CompareOpenPolygons(AFirstList, ASecondList);
    ASecondList.Reverse;
  end;
end;

function IsListEqual(AList1, AList2: TCadVec3List; AOffset: Integer): Boolean;
var
  i1, i2, iCount: Integer;
begin
// AOffset .. StartIndex in List2,
  iCount := AList2.Count;
  i2     := AOffset;
  for i1 := 0 to AList1.Count - 1 do
  begin
    if not IsListItemEqual(AList1[i1], AList2[i2]) then
      Exit(False);

    i2 := (i2 + 1) mod iCount;
  end;
  Result := True;
end;

function IsListItemEqual(AItem1, AItem2: TCadVec3): Boolean;
begin
  Result := (AItem1.x = AItem2.x) and (AItem1.y = AItem2.y) and (AItem1.z = AItem2.z);
end;

procedure RemoveIdenticalFromList(AList: TCadVec3List; AOpen: Boolean);
var
  i: Integer;
begin
// Entferne doppelte, aufeinander Folgende Eintr&#65533;ge bzw. Elemente.
  for i := AList.Count - 1 downto 1 do
  begin
    if IsListItemEqual(AList[i], AList[i - 1]) then
      AList.Delete(i);
  end;

  if not AOpen then
  begin
    if IsListItemEqual(AList.First, AList.Last) then
      AList.Delete(AList.Count - 1);
  end;
  AList.TrimExcess;
end;

end.


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:23 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