Performance-Problem mit for-Schleifen bei sehr vielen Zeilen
tommy0078
Gast
Beiträge: ---
Anmeldedatum: ---
Wohnort: ---
Version: ---
Verfasst am: 29.07.2015, 14:12
Titel: Performance-Problem mit for-Schleifen bei sehr vielen Zeilen
Hallo Zusammen,
dies ist meine erste Frage hier im Forum, da ich ein Performance-Problem mit meinem mfile habe und hoffe, dass es dafür eine Lösung gibt. Mein Programm funktioniert bei Matrizen mit bis zu ca. 100.000 Zeilen von der Geschwindigkeit akzeptabel. Darüber wird es aber viel zu langsam. Zur Information: Ich nutze Matlab 7.11.0 (R2010b). Mein verbauter Arbeitsspeicher im PC beträgt 8 GB.
Kurz zur Aufgabe: Ich nehme Messwerte über knapp 2 Stunden auf und lese diese ohne Probleme in MATLAB ein. Dabei erhalte ich beispielsweise einmal eine Matrix mit 3.467.206 Zeilen (abhängig von der Messdauer) und 17 Spalten (immer konstant, da Messgrößen). Diese verarbeite ich mit for-Schleifen, was das Problem darstellt, da diese viel zu langsam sind. Eine Alternative dazu habe ich allerdings nicht im Internet gefunden. Die Speicherung der Variablen erfolgt als struct, da ich mehrere Messungen durchführe und gleichzeitig einlesen möchte. Beispielsweise sieht die Benennung wie folgt aus: Messdaten_Zeitstempel_Unix.Messung1 und Messdaten_Zeitstempel_Unix.Messung2. Die Matrix in dem struct Messdaten_Zeitstempel_Unix.Messung1 liegt als double vor.
Zunächst möchte ich die ganze Zeile der Matrix löschen, wenn doppelte Werte in der ersten Spalte auftreten und Werte, die kleiner sind als der vorige Eintrag in der ersten Spalte. Dazu nutze ich die Matrix Messdaten_Zeitstempel_Unix, die die eingelesenen Daten enthält und generiere daraus eine neue Matrix Messdaten_ohne_Doppeleintraege. Die for-Schleife mit dem Index j kann man lassen, da diese nur die Anzahl der ausgewählten Messdaten enthält und definiert wie oft der Code durchlaufen werden muss. Maximal werden 15 Messungen durchgeführt und somit (meiner Meinung nach) ausreichend wenig, dass die Performance nicht gestört wird. Mein Code sieht dazu wie folgt aus:
Code:
for i = 1:length(Messdaten_Zeitstempel_Unix.(names{j})) if i == 1
Messdaten_ohne_Doppeleintraege.(names{j})(i,:) = Messdaten_Zeitstempel_Unix.(names{j})(i,:);
elseif Messdaten_Zeitstempel_Unix.(names{j})(i,1) > max(Messdaten_ohne_Doppeleintraege.(names{j})(:,1))
Messdaten_ohne_Doppeleintraege.(names{j})(end+1,:) = Messdaten_Zeitstempel_Unix.(names{j})(i,:);
end end
Zusätzlich möchte ich in der ersten Spalte den Unix-Timestamp in Messzeit umrechnen. Das mache ich wie folgt:
Code:
for i = 1:length(Messdaten_ohne_Doppeleintraege.(names{j})) if i == 1
Zeitstempel_Sekunden.(names{j})(i,1) = 0;
else
Zeitstempel_Sekunden.(names{j})(i,1) = (((Messdaten_ohne_Doppeleintraege.(names{j})(i,1) - Messdaten_ohne_Doppeleintraege.(names{j})(i-1,1))/1000000) + Zeitstempel_Sekunden.(names{j})(i-1,1));
end end
Messdaten_ohne_Doppeleintraege.(names{j})(:,1) = Zeitstempel_Sekunden.(names{j});
Das müsste doch auch ohne for-Schleife zu lösen sein?!
Und jetzt kommt der komplizierteste Teil: Ich möchte die Drehwinkel um die x- Achse und y-Achse einer Ebene im Vergleich zur senkrechten des Gravitationsvektors berechnen. Das mache ich wie folgt:
Code:
for i = 1:length(Messdaten_Zeitstempel_Sekunden.(names{j})) if Messdaten_Zeitstempel_Sekunden.(names{j})(i,1) <= 1% Bis 1 Sekunde nach Messbeginn wird die Lage statisch berechnet
start.(names{j}) = i; % Endezeile finden bei der die Messzeit von 1 Sekunde überschritten ist
gyz.(names{j}) = sqrt(Messdaten_Zeitstempel_Sekunden.(names{j})(i,9)^2+Messdaten_Zeitstempel_Sekunden.(names{j})(i,10)^2);
gxz.(names{j}) = sqrt(Messdaten_Zeitstempel_Sekunden.(names{j})(i,8)^2+Messdaten_Zeitstempel_Sekunden.(names{j})(i,10)^2);
gall.(names{j}) = sqrt(Messdaten_Zeitstempel_Sekunden.(names{j})(i,8)^2+Messdaten_Zeitstempel_Sekunden.(names{j})(i,9)^2+Messdaten_Zeitstempel_Sekunden.(names{j})(i,10)^2);
was sagt denn der profiler wo die rechenzeit drauf geht? bringt ja nix sachen zu optimieren die keine zeit kosten.
Zitat:
Maximal werden 15 Messungen durchgeführt und somit (meiner Meinung nach) ausreichend wenig, dass die Performance nicht gestört wird.
das lässt sich doch sehr leicht am profiler ablesen
Zitat:
Mein Programm funktioniert bei Matrizen mit bis zu ca. 100.000 Zeilen von der Geschwindigkeit akzeptabel. Darüber wird es aber viel zu langsam.
hört sich ja nache einem speicherproblem an. mit der aussage 8gb ram alleine ist es nicht getan. hast du denn eine 64 bit version von matlab. wie viel ram steht denn matlab zur verfügung. fehlende präallokation ist etwas das sich erst bei großen matriten bemerkbar macht dann aber immer mehr ins gewicht fällt.
_________________
Danke für die sehr schnelle Antwort und den Tipp mit dem Profiler. Diesen kannte ich bis jetzt noch nicht. Er zeigt aber nichts an, da das Programm in akzeptabler Zeit nicht fertig wird.
Da ich bisher nur eine Messung durchgeführt habe, kann ich nicht testen, ob die 15 Messungen ins Gewicht fallen. Aber bei der einen Messung tritt das Problen schon auf, dass er zu lange rechnet.
Nach Untersuchung der Schleife zum Entfernen der Doppeleinträge, hängt es schon da. Wenn er diese durchläuft, braucht er ewig. Nach ein paar Minuten habe ich das abgebrochen. Zum Testen habe ich tic und toc so eingebaut:
Code:
for i = 1:length(Messdaten_Zeitstempel_Unix.(names{j})) tic if i == 1
Messdaten_ohne_Doppeleintraege.(names{j})(i,:) = Messdaten_Zeitstempel_Unix.(names{j})(i,:);
elseif Messdaten_Zeitstempel_Unix.(names{j})(i,1) > max(Messdaten_ohne_Doppeleintraege.(names{j})(:,1))
Messdaten_ohne_Doppeleintraege.(names{j})(end+1,:) = Messdaten_Zeitstempel_Unix.(names{j})(i,:);
end toc end
Zu Beginn benötigt Matlab 0,00xx Sekunden. Der Wert steigt nach ein paar Minuten rechnen auf 0,06 Sekunden an. Bei 3,4 Millionen Messwerten kommt bei einer durchschnittlichen Rechenzeit von 0,06 Sekunden schon eine ordentliche Rechenzeit zusammen.
Meine Matlab Version ist eine 32-bit Version und memory gibt folgendes aus:
Code:
Maximum possible array: 2046 MB (2.146e+009 bytes) *
Memory available forall arrays: 3477 MB (3.646e+009 bytes) **
Memory used by MATLAB: 357 MB (3.743e+008 bytes)
Physical Memory(RAM): 8075 MB (8.467e+009 bytes)
Wenn ich das mit der Präallokation richtig verstanden habe, sollte ich die neu zu erstellende Matrix zuvor definieren? Das werde ich mal testen und gucken, ob sich an der Rechenzeit etwas ändert.
Er zeigt aber nichts an, da das Programm in akzeptabler Zeit nicht fertig wird.
diese aussage verstehe ich nicht.
Zitat:
Wenn ich das mit der Präallokation richtig verstanden habe, sollte ich die neu zu erstellende Matrix zuvor definieren? Das werde ich mal testen und gucken, ob sich an der Rechenzeit etwas ändert.
das sollte es eigendlich. das von dir beschriebene passt genau auf den sachverhalt da du in jedem schleifenaufruf ein neues array anlegst den alten inhalt kopierst und die speicher wieder freigiebst.
in dieser zeile
Messdaten_ohne_Doppeleintraege.(names{j})(end+1,:) = Messdaten_Zeitstempel_Unix.(names{j})(i,:);
mit
unique
und logischem indizieren kann man das vielleicht komplett umgehen.
desweiteren ist so ein konstrukt:
if i==1
nicht sehr gut. es ist nur im ersten schleifendruchlauf true.. wird aber in jedem durchlauf abgefragt. das kann man auch einfach rauszihenen und dann bei 2 anfangen.
_________________
Verfasst am: 29.07.2015, 15:38
Titel: Re: Performance-Problem mit for-Schleifen bei sehr vielen Ze
Hallo tommy0078,
Das sind sehr viele Informationen auf einmal. Ich picke mir mal ein Detail heraus.
Zitat:
Code:
for i = 1:length(Messdaten_Zeitstempel_Unix.(names{j})) if i == 1
Messdaten_ohne_Doppeleintraege.(names{j})(i,:) = Messdaten_Zeitstempel_Unix.(names{j})(i,:);
elseif Messdaten_Zeitstempel_Unix.(names{j})(i,1) > max(Messdaten_ohne_Doppeleintraege.(names{j})(:,1))
Messdaten_ohne_Doppeleintraege.(names{j})(end+1,:) = Messdaten_Zeitstempel_Unix.(names{j})(i,:);
end end
Der wiederholte Zugriff auf "Messdaten_ohne_Doppeleintraege.(names{j})" verbraucht eine Menge Zeit und ist außerdem schlecht zu lesen.
In jeder Iteration zu prüfen, ob i==1 ist, ist überflüssig. Wenn es für i==1 eine Ausnahme gibt, ziehe sie vor die Schleife:
Code:
Time = Messdaten_Zeitstempel_Unix.(names{j});
Dups = Messdaten_ohne_Doppeleintraege.(names{j});
Dups(1,:) = Time(1,:); % Erste Iteration: i==1 for i = 2:length(data) if Time(i,1) > max(Dups(:,1))
Dups(end+1,:) = Time(i,:);
end end
So, jetzt kann man das zumindest schon mal lesen, ohne den Brillen-Zitterich zu bekommen. Ein Zugriff auf "Messdaten_Zeitstempel_Unix.(names{j})" erfordert eine Menge Operationen: Matlab muss zunächst die Liste mit Feld-Namen finden, dann den Index des Feldes. Den Inhalt des Cell-Strings muss man auch zunächst mal finden.
Im nächsten Schritt würde ich das wiederholte Suchen nach dem Maximum weglassen.
Code:
Dups(1,:) = Time(1,:); % Erste Iteration: i==1
MaxDups = max(Dubs(:,1));
for i = 2:length(data) if Time(i,1) > maxDups
Dups(end+1,:) = Time(i,:);
MaxDups = Time(i, 1);
end end
Das nächste Problem ist das iterative Anwachsen von Dups. Das ist ein Desaster! Es muss jedes mal ein neues Array allociert werden und das alte kopiert. Wenn Dups zum Schluss 1000 Zeilen hat mit N Spalten, werden sum(1:1000)*N*8 Bytes alloziert und fast eben so viele kopiert. Und das ist extrem resourcen-hungrig.
Also unbedingt pre-allozieren!
Code:
Dups = zeros(size(Time)); % Das ist die maximal mögliche Größe, oder?
nDups = 1;
Dups(1,:) = Time(1,:); % Erste Iteration: i==1
MaxDups = max(Dubs(:,1));
for i = 2:length(data) if Time(i,1) > maxDups
nDups = nDups + 1;
Dups(nDups,:) = Time(i, :);
MaxDups = Time(i, 1);
end end
Dups = Dups(1:nDups, :); % Nicht benötigtes entfernen
Es ist bestimmt nicht das wichtigste Ziel FOR-Schleifen wegzulassen. Das Problem sibnd die wiederholten Berechnungen, die eigentlich in jeder Iteration identisch sind. Dies ist zeitraubend, weil es wirklich zu nichts nützlich ist. Das Vektorisieren ist dann erst ein weiterer Schritt, den man erst beginnt, wenn man einen Code-Abschnitt eindeutig als Flaschenhals für die Laufzeit bestimmt hat.
Gruß, Jan
tommy0078
Gast
Beiträge: ---
Anmeldedatum: ---
Wohnort: ---
Version: ---
Verfasst am: 29.07.2015, 16:39
Titel:
@ Winkow: Herzlichen Dank für die Information! Zu meiner Aussage: Das mfile ist beim Einlesen der Messdatei nicht vollständig durchgelaufen, da er sich in der Schleife "aufgehängt" hat. Deshalb hatte mir der Profiler nichts zur Rechenzeit angezeigt. Es kann natürlich auch sein, dass ich diesen nicht richtig genutzt habe. Mit einer kurzen Messdatei zum Testen hats aber geklappt mit der Anzeige der Laufzeiten...
@ Jan: Herzlichen Dank für Deine ausführliche Erklärung der einzelnen Schritte. Ich konnte erfreulicherweise alles nachvollziehen und habe, in einem neuen mfile, die von Dir vorgeschlagenen Änderungen eingebaut. Zur Auswertung des Performance-Gewinns habe ich eine wenige Minuten umfassende Messung verwendet, die zum Testen dient. In der Version von mir sind ca. 60 Sekunden Rechenzeit notwendig. Nach den Änderungen von Dir sind nur noch ca. 30 Sekunden Rechenzeit notwendig. Dann lag es tatsächlich nicht an den for-Schleifen, sondern an unsauberer Programmierung. Bin halt kein Informatiker...
Die restlichen for-Schleifen werde ich auch noch versuchen zu optimieren und bei Problemen werde ich mich dann wieder melden. Heute wird das aber nix mehr.
Nochmals herzlichen Dank an euch beide!
Einstellungen und Berechtigungen
Du kannst Beiträge in dieses Forum schreiben. Du kannst auf Beiträge in diesem Forum antworten. Du kannst deine Beiträge in diesem Forum nicht bearbeiten. Du kannst deine Beiträge in diesem Forum nicht löschen. Du kannst an Umfragen in diesem Forum nicht mitmachen. Du kannst Dateien in diesem Forum posten Du kannst Dateien in diesem Forum herunterladen
MATLAB, Simulink, Stateflow, Handle Graphics, Real-Time Workshop, SimBiology, SimHydraulics, SimEvents, and xPC TargetBox are registered trademarks and The MathWorks, the L-shaped membrane logo, and Embedded MATLAB are trademarks of The MathWorks, Inc.