![]() |
Code Optimierung
Hallo zusammen,
ich habe hier einen ganz kleinen Algorithmus den ich gerne noch etwas optimieren würde, da er sehr zeitkritisch ist. Hier mal die interessanten Stellen:
Delphi-Quellcode:
while sl.Count > 0 do
begin SetLength(IDArray, Length(IDArray) + 1); IDArray[High(IDArray)].ID := Copy(sl.Strings[0], 0, Pos(';', sl.Strings[0]) - 1); IDArray[High(IDArray)].Sum := IDArray[High(IDArray)].Sum + StrToInt32_JOH_IA32_7_a(ExtractPZN(sl.Strings[0])); sl.Delete(0); for i := sl.Count - 1 downto 0 do begin if IDArray[High(IDArray)].ID = ExtractID(sl.Strings[i]) then begin IDArray[High(IDArray)].Sum := IDArray[High(IDArray)].Sum + StrToInt32_JOH_IA32_7_a(ExtractPZN(sl.Strings[i])); sl.Delete(i); end; end; end;
Delphi-Quellcode:
Es geht darum durch diesen Algo knapp 13 Millionen Zeilen zu jagen ;)
function ExtractPZN(const Value: string): string; inline;
begin Result := Copy(Value, 9, 7); {$MESSAGE Warn 'Methode geht von einer konstanten Länge der PZN aus.'} end; function ExtractID(const Value: string): string; inline; begin Result := Copy(Value, 0, 7); {$MESSAGE Warn 'Methode geht von einer konstanten Länge der ID aus.'} end; Momentan braucht er für 100.000 Sätze knapp 24 Sekunden. Kann man das noch schneller machen? Achja die seltsamen IntToStr-Methoden sind Replacements vom FastCodeprojekt und haben schon ziemlich viel gebracht :) |
Re: Code Optimierung
Zieh das SetLength vor die Schleife. Das bewirkt Wunder.
|
Re: Code Optimierung
Setzte die Größe des dynamischen Arrays vor der Schleife einmalig.
|
Re: Code Optimierung
Ich weiß aber nicht wie groß das Array wird... :(
|
Re: Code Optimierung
Obwohl also ich könnte natürlich eine statisches Array draus machen mit ner Größe die auf jeden Fall reicht. Macht das Sinn?
|
Re: Code Optimierung
Falls das Array vor der Schleife die Länge 0 hat, hat es danach höchstens so lang wie die Stringliste vor der Schleife. Also kannst du die Länge vor der Schleife auf diese obere Schranke setzen, in der Schleife dann mit einer Variablen mitzählen und nachher dann anpassen. Das sind dann nur noch zwei SetLength-Aufrufe.
Des Weiteren würde ich empfehlen, getrennte Daten getrennt zu speichern (z.B. in Records - das spart die Copy-Aufrufe) und eine Zahl auch als Zahl zu speichern. |
Re: Code Optimierung
High(IDArray) wird ziemlich oft ermittelt, speicher den Wert einmal in einer Variabel und benutze diese, dürfte immer Length(IDArray) + 1 sein (?), muss dann nur einmal pro Schleifendurchlauf der äußeren Schleife ermittelt werden.
Ändert sich die Größe des Arrays während der Laufzeit? Ist sl sortiert? Wenn ja, dann müsstest Du einen Algorhythmus finden können, der ohne sl.delete(0) auskommt. Eventuell ist ein sl.sort und dann sequentiell abarbeiten schneller. Kannst Du die zu erwartende Größe des Array vorher bestimmen, dann musst Du sie nur einmal setzen. (Könnte ein Nebeneffekt des Sortierens sein, dass die Ermittlung vereinfacht wird. (= Anzahl der ID's?)) |
Re: Code Optimierung
Zitat:
Lieber einmal größer initialisieren und am Ende kürzen |
Re: Code Optimierung
Zitat:
Und wo speichere ich eine zahl nicht als Zahl? eher andersherum ;) Einen string als Zahl ;) Das Setzen von SetLength vor der Schleife hat bei 100.000 Sätzen noch nicht wirklich was gebracht, aber ich denke bei mehr sollten das was bringen. Zitat:
|
Re: Code Optimierung
Natürlich speicherst du Zahlen als Strings. Deshalb brauchst du ja eine StrToInt-Funktion. ;-)
Und diese rufst du für die PZN-Strings mehrfach auf. Also ist es effektiver, wenn du aus deinem String einmal die Daten extrahierst und sie in einem Record speicherst. |
Re: Code Optimierung
Hm...Das verstehe ich nicht... Wo extrahiere ich denn zwei Mal die selben Daten?
|
Re: Code Optimierung
Hallo,
Du gehst von einer konstanten Länge der ID aus und diese steht auch noch an erster Stelle 0..6 der Strings in der SL. Dann kannst Du also die Stringlist vorher sortieren und diese dann der Reihe nach abarbeiten und brauchst nur einmal die SL abklappern (lineare Laufzeit), wie nahpets geschrieben hat, statt immer komplett die verbliebene SL (Quadratische Laufzeit). Alternativ ein customsort entwickeln. Bei sortierter Liste ist es ganz simpel. ... Steze Länge des Feldes auf sl.count (oder einer praxisnahen Faktor davon) j=0 Wiederhole ID[j] einlesen, merken in LaufendeID Summe auf Wert von PZN setzen, erhöhe i solange i < sl.count falls ID = LaufendeID dann erhöhe sum um PZN sonst erhöhe j mache oben hinter wiederhole weiter ... Gruß Horst |
Re: Code Optimierung
Ist es denn sinnvoll 13 Millionen Einträge vorher zu sortieren? wenn ja wäre das natürlich super :)
|
Re: Code Optimierung
Zitat:
Du rechnest momentan mit einer Laufzeit von 13 * 24 Sekunden (ca.) = 5,irgendwas Minuten. Dauert das sl.sort deutlich weniger, solltest Du es mit dem Sortieren und dann sequentiellem abarbeiten versuchen. |
Re: Code Optimierung
Hm, du hast recht, ich habe mich getäuscht. Aber eines fällt noch auf: Einmal extrahierst du die ID, indem du mit Pos nach dem Delimiter suchst; sonst nimmst du an, dass die Länge der ID konstant ist. Entweder du machst diese Annahme gar nicht oder immer!
Und dem Vorschlag, die List nach der ID zu sortieren, kann ich mich nur anschließen. Du erreichst damit eine Laufzeit O(n log n) statt O(n²). Nebenbei würde mich mal interessieren, wer sich dieses Format ausgedacht hat und woher die Daten kommen. |
Re: Code Optimierung
Ich hab das nicht genau analyisert, aber du hast eine Liste mit irgendwelchen Daten und willst die nun unifizieren, also alle doppelten Einträge raussschmeissen. Richtig?
Egal, denn du hast einen Aufwand von O(n^2), die Laufzeit wächst also quadratisch mit der Listenlänge. Das ist schlecht, denn es geht viel schneller: Die erste Idee ist ja, die Liste zu sortieren, und dann einfach von vorne durch und die mehrfachvorkommen eliminieren. Das wäre in O(n log n) zu machen (verdoppelung der Listenlänge = 2,2x Zeit). Schon recht ordendlich. Aaaaber: Man sortiert vorher und benötigt die sortierten Daten eigentlich nicht, also ein algorithmischer Overkill. Ich würde es mit einer Hashmap machen. Das ist eine Datenstruktur, mit der man verdammt schnell Daten speichern und wiederfinden kann. Damit wäre das in O(n) zu machen, was dann optimal wäre, denn schneller geht es nicht: Du musst schließlich einmal durch die ganze Liste durch... Im Prinzip sieht das so aus:
Delphi-Quellcode:
Das ist jetzt Pseudocode, aber ich denke, Du verstehst das Prinzip. Eine Hashmap findest du
For Element in Liste Do
If not Hashmap.Find (Element.ID) Then Begin// Wenn wir diese ID noch nicht haben SaveToFinalList (element); Hashmap.Add (Element.ID); End; ![]() Da Du gut programmieren kannst, bekommst Du das sicherlich ohne Probleme hin. |
Re: Code Optimierung
Hallo,
noch eine andere Idee:
Delphi-Quellcode:
Das sollte es gewesen sein (nicht getestet).
// Du hast eine ID von 7 Zeichen Länge.
// Da sie numerisch ist, kann sie alle ganzzahligen Werte von 0 bis 9999999 enthalten. // Gehe davon aus, dass grundsätzlich jede der möglichen IDs auch vorkommen kann. Const min = 0; max = 9999999; // Definiere ein Array entsprechender Größe: Var IDArray : Array[min..max] of Cardinal; // Gehe davon aus, dass die Summe aus positiven Integerwerten gebildet wird. iID : integer; // ID aus der Liste zur weiteren Verarbeitung. i : Integer; // Schleifenzähler // Wenn Du nun die ID liest, kannst Du sie direkt als Index // für das Array benutzen und muss sie nicht extra im Array speichern, // da Index und ID identisch sind. begin for i := min to max do IDArray[i] := 0; // Damit wir einen definierten Inhalt haben. for i := 0 to sl.count - 1 do begin iID := ExtractID(sl.Strings[i]); IDArray[iID] := IDArray[iID] + StrToInt32_JOH_IA32_7_a(ExtractPZN(sl.Strings[i])); end; end; Stephan |
Re: Code Optimierung
Zitat:
Ich möchte zwar nicht die doppelten Einträge löschen sondern nur suchen und den zweiten Teil des Eintrages (die PZN) ausummieren. Aber ist ja prinzipiell kein großer Unterschied. Zitat:
VOn der Idee her aber genial :) |
Re: Code Optimierung
Nochmal zu der Hashmap.... Könnte ich die auch so verwenden wie das mit dem Array gezeigt wurde? Also Liste durchgehen, ID als Index in der Hasmap speichern und dann als Data die PZN. Dann über Find gucken ob die ID schon vorhanden ist, und wenn ja dann addieren, wenn nein dann enben hinzufügen. Wäre das schnell? Wie könnte ich denn den Dataoutpout bei Find verändern? Einfach den Wert an der Adresse des Pointers ändern? Geht das?
@alzaimar: Du bist übrigens der erste Mensch außer meinem Info-Lehrer der mich auf die O-Notation anspricht ;) Ich hätte nicht gedacht, dass ich das so schnell wiederhöre :) |
Re: Code Optimierung
So, ich hätte jetzt folgenden Code:
Delphi-Quellcode:
Sollte das so klappen?
for i := 0 to sl.Count - 1 do
begin if not StringDic.Find(ExtractID(sl.Strings[i]), Data) then begin tmpPZN := StrToInt32_JOH_IA32_7_a(ExtractPZN(sl.Strings[i])); Data := @tmpPZN; StringDic.Add(ExtractID(sl.Strings[i]), Data); end else begin Integer(Data^) := Integer(Data^) + StrToInt32_JOH_IA32_7_a(ExtractPZN(sl.Strings[i])); end; end; |
Re: Code Optimierung
Hallo,
wenn Du kein Array der passenden Größe machen kannst, dann mach doch 100. Eins für 0 bis 99999, eins für 100000 bis 199999 .... Per Case könntest Du dann nach iID-Bereich abfragen und entscheiden, in welches Array Du die Summe speichern muss. Naja, diese Lösung ist dann suboptimal und grenzt schon an Gefrickel, aber wenn's funktioniert... Wofür wird bitteschön der ganze Spaß gebraucht und wie ist mit der Verteilung der IDs zu rechnen? Gibt es da irgendwelche Bereiche von IDs, die überhaupt nur infrage kommen. Dann läßt sich das Array ja durch anpassen von min und max auf diesen Bereich einschränken ohne dass die übrige Logik davon betroffen wird. Zumindest von der Zeilenzahl der Liste, kann jede ID 1,3mal vorkommen. Die Hashmap muss dann auch entsprechend groß werden können. Stephan |
Re: Code Optimierung
Ähm naja das sind Daten aus dem medizinischen Bereich. Kann ich euch also nicht allzu viel zu sagen.
Es geht nur darum erst mal alle doppelten zu finden und zu addieren und danach alle IDs die die selbe Summe an PZNs haben zu einer Gruppe zusammenzufassen. Die Hashmap ist groß genug, die vergrößert sich von selber :) |
Re: Code Optimierung
--
|
Re: Code Optimierung
Zitat:
Mach es so:
Delphi-Quellcode:
Achtung! Hinterher aber die Data wieder mit Dispose freigeben! Dazu wanderst Du per 'First/Next durch die Hashmap und gibst einzeln die gelieferten Data wieder frei.
Type
PInteger = ^Integer; Var Data : PInteger; sID : String; for i := 0 to sl.Count - 1 do Begin tmpPZN := StrToInt32_JOH_IA32_7_a(ExtractPZN(sl.Strings[i])); sID := ExtractID(sl.Strings[i]); if not StringDic.Find(sID, Pointer (Data)) then begin New (Data); Data^ := tmpPZN; StringDic.Add(sID, Data); end else Inc(Data^, tmpPTN); end;
Delphi-Quellcode:
Große Arrays erzeugt man übrigens dynamisch.
StringDic.First;
While StringDic.Next (sID, PInteger (Data)) Do Dispose (Data);
Delphi-Quellcode:
Var
BigArray : Array Of Integer; Begin SetLength(BigArray, 10000000); |
Re: Code Optimierung
Achs
Zitat:
Okay vielen Dank erst einmal :) Dann werde ich mich mal damit beschäftigen ;) Wenn man das Array dynamisch erzeugt klappts dann auch größer oder was? :) |
Re: Code Optimierung
Also das mit dem Array scheint nicht zu funktionieren, dafür das mit der Hashmap aber umso besser :)
Also wenn die Daten die da jetzt rausgekommen sind wirklich stimmen, dann :shock: . Er braucht jetzt für die 13 Millionen Einträge kürzer als vorher für 100.000 :lol: . Also vielen Dank schonmal :) :) . Das war ja eine super Hilfe :dp: |
Re: Code Optimierung
Hmmm... Ich habe noch etwas Probleme, die Werte aus der Map auszulesen...
Hier mal mein Code:
Delphi-Quellcode:
So, an sich klappt das nur leider bekomme ich dann Integer Überläufe angezeigt, also halt negative Werte.
StringDic.First;
ListBox1.Items.BeginUpdate; for i := 0 to StringDic.TotalCount - 1 do begin StringDic.Next(sID, tmpData); ListBox1.Items.Add('ID: ' + sID + ' Summe: ' + IntToStr(Integer(tmpData^))); end; ListBox1.Items.EndUpdate; Nehme ich nun beim Casten Int64 anstatt Integer, so kommen vollkommen falsche Werte herraus. Was kann man dagegen tun? |
Re: Code Optimierung
Okay, Problem gelöst. Ich musste natürlich Data nicht als PInteger sondern als PInt64 deklarieren.
Hier der gesamte Code:
Delphi-Quellcode:
Vielen Dank :)
var
StringDic : TStringDictionary; tmpPZN : Integer; Data : PInt64; sl : TStringList; sID : string; [...] for i := 0 to sl.Count - 1 do begin tmpPZN := StrToInt32_JOH_IA32_7_a(ExtractPZN(sl.Strings[i])); sID := ExtractID(sl.Strings[i]); if not StringDic.Find(sID, Pointer(Data)) then begin New(Data); Data^ := tmpPZN; StringDic.Add(sID, Data); end else begin Inc(Data^, tmpPZN); end; end; |
Re: Code Optimierung
Delphi-Quellcode:
Bei mir funktioniert das tadellos auch mit PInteger. Hast Du Data etwa schon wieder per Dispose freigegeben?
Var
tmpData : PInteger; Begin StringDic.First; ListBox1.Items.BeginUpdate; while StringDic.Next(sID, tmpData) do ListBox1.Items.Add('ID: ' + sID + ' Summe: ' + IntToStr(tmpData^)); ListBox1.Items.EndUpdate; End; |
Re: Code Optimierung
Ich darf halt tmpData nicht als PInteger deklarieren... Dann gibt er mir den fehler dass die Parameter stimmen müssen. Ich brauche also einen untyped Pointer. Außerdem reicht Integer halt nicht aus von der Größe. Bei 13 Millionen Einträgen kommt da was zusammen ;)
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 21:41 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