Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Kleiner Optimierungstest (https://www.delphipraxis.net/72910-kleiner-optimierungstest.html)

Stefan Hueg 9. Jul 2006 07:15


Kleiner Optimierungstest
 
Ich habe mir mal ein paar Gedanken über die Optimierung des Sourcecodes gemacht und dabei einige Tests angestellt. Hier die Resultate:

Test1:
Delphi-Quellcode:
if String = '' then
...
gegen
Delphi-Quellcode:
if Length(String) > 0 then
...
Resultat: Ersteres rund 1/3 schneller


Test2:
Delphi-Quellcode:
if Control.Checked = True then
...
gegen
Delphi-Quellcode:
if Control.Checked then
...
Resultat: Letzeres rund 1.3% schneller


Test3:
Delphi-Quellcode:
IntegerValue := Ord(True);
gegen
Delphi-Quellcode:
IntegerValue := 1;
Resultat: Ersteres rund 7.3% schneller


Test4:
Delphi-Quellcode:
BoolValue := Boolean(1);
gegen
Delphi-Quellcode:
BoolValue := True;
Resultat: Ersteres rund 5.79% schneller


Test5:
Delphi-Quellcode:
BoolValue := Not False;
gegen
Delphi-Quellcode:
BoolValue := True;
Resultat: Ersteres rund 7% schneller


Test6:
Delphi-Quellcode:
B := Not B;
gegen

Delphi-Quellcode:
if B = True then
  B := False
else
  B := True;
Resultat: Ersteres rund 14.1% schneller


Nun, das finde ich ziemlich beeindruckend, testet es mal selbst. Solch ein Ergebnis hätte ich nicht gedacht. Vielleicht sind meine Messungen auch falsch. Ansonsten: Kennt ihr noch irgendwelche Optimierungsmethoden?

EDIT: Getestet habe ich indem ich die zu testende Operation 5 sek. durchgeführ, das ganze 5 Mal wiederholt und dann den Durchschnitt gebildet habe.

Gruß,

Stefan

Go2EITS 9. Jul 2006 07:28

Re: Kleiner Optimierungstest
 
@Stefan
Hey, nicht schlecht! Das hätte ich wirklich nicht gedacht.
Wenn neue Test dazukommen, sei doch so nett und schreibe in der ersten Nachricht Deines Threads weiter!

Falls Du magst, stelle doch Dein Testskelett zur Verfügung. Dann muss nicht jeder alles neu programmmieren.

Ich werde in jedem Fall Deine Liste übernehmen. Optimierungen auf der Ebene dürften sich
merklich auf die Programm Performance auswirken. Ganz Besonders in Schleifen.

Super Sache! Ich bin auf die weitere Resultate gespannt.

:hi:
Go2EITS

Sharky 9. Jul 2006 07:41

Re: Kleiner Optimierungstest
 
Zitat:

Zitat von Stefan Hueg
Test3:
Delphi-Quellcode:
IntegerValue := Ord(True);
gegen
Delphi-Quellcode:
IntegerValue := 1;
Resultat: Ersteres rund 7.3% schneller
.

Hai Stefan,

das kann eigentlich nicht sein!
Wenn ich mir beide Zuweisunen im Debugger ansehe steht dort immer
Code:
move eax, $00000001
Es wird also immer der selbe Code ausgeführt.

Und bei Test 5 macht der Compiler in beiden Fällen daraus ein
Code:
move al, $01
Getestetet mit aktivierter Codeoptimierung.

Bernhard Geyer 9. Jul 2006 08:35

Re: Kleiner Optimierungstest
 
Ohne jetzt dein Testprogramm als ganzes zu sehen und mit der Assembler-Aussage von Sharky sage ich jetzt einfach das du zwar schön gemessen hast aber deine Schlußfolgerung falsch ist. Sie muss (für einige der Tests) heißen:

Ein Multitaskingsystem wie Windows hat für gleiche triviale Aufgaben Laufzeitunterschiede von ca. x%.

Wie wird die benötigte Rechenzeit (Laufzeit ist Aufgrund der fehlenden Echtzeitfähigkeit nicht Aussagekräftig) bestimmt? Mittels Uhrzeitabfrage ist Falsch. Du mußt eine bestimmte Aufgabe durchführen und am Anfang und Ende die Windows-Performancecounter abfragen. Damit bekommst Du eine bessere Aussage über die benötigte Rechenzeit.

Auch sind die Prozentangaben (wenn denn wirklich unterschiedlicher Assemblercode rauskommt) Prozessorabhänig. Ein aktueller AMD-Prozessor benötigt für Realsierung X 5% weniger und ein Intel-Prozessor 3% mehr.

Es gibt im Internet (URL im Moment nicht verfügbar) die sich mit Optimierungen für Delphi für diverse Prozessoren tiefer auseinandersetzt. Hier wird neben Mathematischen Funktionen wie sin/cos auch Stringoperationen optimiert.

[EDIT]FASTCODE PROJECT[/EDIT]

Der_Unwissende 9. Jul 2006 09:07

Re: Kleiner Optimierungstest
 
Hi,
nebst dass ich den beiden vorhergehenden Beiträgen einmal voll zustimmen möchte, wollte ich hier noch sagen, dass ich bei dir keine Optimierungen sehe.
Ich meine klar, ist schon interessant zu sehen, dass deine Programme je nach Auslastung unterschiedliche Zeiten verbrauchen. Hier wäre es natürlich wichtig zu wissen wie viele Läufe mit jeder Konfiguration du gemacht hast und welche Optimierungen du aktiviert hast (oder ebend nicht).
Aber viel wichtiger ist, es ist nichts optimiert. Wenn ich eine if-Abfrage um 2% schneller gestalten könnte (egal wie), von was für eine Anzahl von Takten sprechen wird denn dann bitte? Und wieviel Zeit ging da bisher verloren? Ich denke die wirklich aufwändigen Operationen finden doch woanders statt.
Zudem stellt sich für mich die Frage, warum du hier die Verminderung von Rechenzeit mit Optimierung gleichsetzt. Optimaler Code sollte vorallem verständlich sein. Es ist wirklich eine der wichtigsten Eigenschaften von guten Code, dass er lesbar/wartbar/erweiterbar/... bleibt. Ob ich dabei 5% Geschwindigkeit auf der Strecke lasse ist letztlich egal. Das ist mit einer der vielen folgenden CPU-Generationen eh vergessen. Wahrscheinlicher ist sogar, dass die Compileroptimierung es eh besser weiß und wie in diesen Beispielen eh den gleichen Code erzeugt.
Zudem läuft die Momentane Entwicklung auf Multithreading hinaus, du hast verschiedene Kerne und mehrere ALUs mit eigenen Pipelines. Hier wird sich kaum ein kleiner Unterschied in so einfachen Abfragen (deiner Beispiele) bemerkbar machen, als vielmehr das schreiben guter nebenläufiger Programme. Je nachdem wie gut du hier synchronisierst, sollte einiges mehr an Unterschied messbar sein und je nach CPU auch nochmal zwischen Single- und Multithreading.

Gruß Der Unwissende

Stefan Hueg 9. Jul 2006 09:26

Re: Kleiner Optimierungstest
 
Ich hätte nicht gedacht dass ich mit meinen Aussagen so sehr gegen eine Mauer fahre. Nun, man sagt ja: Wer einmal Blut geleckt hat...

@Der Unwissende: Natürlich hast du recht, der Code darf bei all seiner Optimiererei die Lesbarkeit nicht verlieren. Aber mich hatte es nunmal interessiert ob der Compiler nen Unterschied zwischen "if Control.Checked = True" oder "if Control.Checked" macht, vermutlich war meine Methodik, wie du schon meintest, dabei falsch. Ich habe einfach die Anzahl von Operationen pro Sekunde gemessen und dann verglichen und mich dabei darauf verlassen, dass die Werte vergleichbar sind.

Gruß,

Stefan

mkinzler 9. Jul 2006 09:29

Re: Kleiner Optimierungstest
 
Es ist ja nicht alles falsch:

Da ja Control.Checked vom Typ Boolean ist macht die 2. Variante
Delphi-Quellcode:
if Control.Checked = True
wenig Sinn.

jbg 9. Jul 2006 11:19

Re: Kleiner Optimierungstest
 
Zitat:

Zitat von Stefan Hueg
Aber mich hatte es nunmal interessiert ob der Compiler nen Unterschied zwischen "if Control.Checked = True" oder "if Control.Checked" macht

Ja den macht er. Und der ist sogar gravierend. Wenn du ersteres benutzt macht er einen direkten Vergleich mit der Zahl 1. Wohingegen bei zweiteren auf ungleich 0 geprüft wird. Nimmt man jetzt die Definition von True her, dann stellt man Fest, das alles True ist, was ungleich False (=0) ist. Mit dem direkten True Vergleich kann es also auch vorkommen (vor allem wenn man von einer WinAPI oder C/C++ Funktion einen Boolean erhält, dass der Compiler "-1 = 1" prüft und somit der Ausdruck im gesamten False ergibt, obwohl die -1 auch ein True-Wert ist.

Delphi-Quellcode:
var
  b: Boolean;
begin
  b := Boolean(-1);
  if b then
    ShowMessage('Wird angezeigt');
  if b = True then
    ShowMessage('Solche Fehler sind später schwer zu finden. Ach ja, der Dialog wird _nicht_ angezeigt.');
end;
Noch ein Wort zum "Str <> ''" vs. "Length(Str) > 0". Nicht nur das zweiteres mehr Tipparbeit ist, es enthält auch einen Funktionsaufruf, der immer Zeit kostet da die Prefetch-Queue des Prozessors neu aufgebaut werden muss. Das "Str <> ''" entspricht vom Code einem "Pointer(Str) <> nil".


Um noch eine Optimierung anzusprechen:
Wenn man bei Parameters, die vom Typ String, Variant und dyn. Array sind, sollte man wenn möglich den "const" Operator benutzen. Dadurch erzeugt der Compiler keinen Extracode, der dafür sorgt, dass beim Eintritt in die Funktion der Referenzzähler des Parameters erhöht wird und beim Austritt (abgesichert durch ein try/finally) wieder heruntergezählt wird.

Christian Seehase 9. Jul 2006 11:19

Re: Kleiner Optimierungstest
 
Moin Stefan,

schau Dir das doch jeweils mal im CPU-Fenster an (jeweils Optimierung aktiviert)
  • Test1: Variante1 sollte schneller verarbeitet werden, da hier einfach weniger passiert.
  • Test2: Dürfte keinen gravierenden Geschwindigkeitsunterschied bringen, aber die zweite Variante ist sauberer.
  • Test3: Wird vom Compiler identisch übersetzt (klar, da ord(true) eine Konstante ist, genau wie die 1), es kann also keinen Geschwindigkeitsunterschied auf Grund des Codes geben. Das heisst für mich: So wie Du es gemessen hast, kann eine Abweichung von 7,3% auch bedeuten, dass es real keinen Unterschied gibt.
  • Test4: Siehe Test3
  • Test5: Siehe Test3
  • Test6: Sieht Test1

Aus Test3 ersehe ich, dass bei Deinem Verfahren die Geschwindigkeitsdifferenzen zu messen, eine Abweichung von mindestens 7,3% keine Aussagekraft hat.

Go2EITS 9. Jul 2006 11:59

Re: Kleiner Optimierungstest
 
@jbg
Delphi-Quellcode:
var
  b: Boolean;
begin
  b := Boolean(-1);
  if b then
    ShowMessage('Wird angezeigt');
  if b = True then
    ShowMessage('Solche Fehler sind später schwer zu finden. Ach ja, der Dialog wird _nicht_ angezeigt.');
end;
Die if b=TRUE Problematik war mir bis vor Kurzem nicht gekannt.
Ich verwende nur noch IF B Then so wie Du es obenstehend aufzeigst.

@Der Unwissende
Ich stimme Dir zu:
1.Code zuerst lesbar und wartbar.
2. "Fehlerfrei"
2. Dann nur Proceduren optimieren, die wirklich Performancegewinn bringen.
3. Und dann Entwicklung des Codes für Multithreading bei Bedarf.

Da stellt sich für mich die Frage, ob eine Procedure, vorausgesetzt, sie wird oft aufgerufen, die lokalen Variablen, da sie ja jedes mal neu angelegt werden müssen, nicht besser global angelegt werden sollen, wie z. B.

Procedure OFT(S:String);
VAR Lokal:String;
begin
...
end;

oder besser

Procedure OFT;
begin
S:=... //Zuweisung/Verarbeitung
String:= //Zuweisung
end;

oder der besser Übersichtlichkeit und Wartung lokal verarbeitet werden.
Dabei habe ich keine Ahnung, ob die untere Version performancemäßig, wirklich etwas bringt.

Wirklich bringt meines Ermessens nach unnötige Funtionsaufrufe wie:
// Die schlechte Version
for I:=1 to length(String)..
statt
// Die gute Version
s:=String;
for i:=1 to S ...

Was mein Ihr?

jbg 9. Jul 2006 13:10

Re: Kleiner Optimierungstest
 
Zitat:

Zitat von Go2EITS
Da stellt sich für mich die Frage, ob eine Procedure, vorausgesetzt, sie wird oft aufgerufen, die lokalen Variablen, da sie ja jedes mal neu angelegt werden müssen, nicht besser global angelegt werden sollen

Damit wird die Prozedur unflexibler, nicht mehr reentrant (was bei Multithreading eine große Rolle spielt) und des Weiteren unleserlicher, weil man sich erst mal die globalen Variablen zusammensuchen muss, die auch noch an anderer Stelle verändert worden sein könnten. (Unter Linux würdes du mit globalen Variablen sogar ein halb-freies CPU Register [ebx] verlieren, weil nur mittels der GOT [global offset table], die in ebx steht, auf globale Variable zugegriffen werden kann).
[edit]Wobei ebx nur in PIC (Position Independent Code) gesperrt ist, was beim ELF Format bei SharedObjects der Fall ist.[/edit]

Zitat:

Dabei habe ich keine Ahnung, ob die untere Version performancemäßig, wirklich etwas bringt.

Wirklich bringt meines Ermessens nach unnötige Funtionsaufrufe wie:
// Die schlechte Version
for I:=1 to length(String)..
statt
// Die gute Version
s:=String;
for i:=1 to S ...
Der Compiler ist schon so schlau, dass er nur ein einziges Mal Length(String) aufruft.

Der_Unwissende 9. Jul 2006 13:17

Re: Kleiner Optimierungstest
 
Zitat:

Zitat von Go2EITS
Wirklich bringt meines Ermessens nach unnötige Funtionsaufrufe wie:
Delphi-Quellcode:
  // Die schlechte Version
  for i := 1 to length(String)..

  // statt
  // Die gute Version

  // denke du meitest eher so etwas
  L := length(String);
  for i := 1 to L ...
Was mein Ihr?

Hier meine ich, dass ein guter Compiler diese Optimierung ohne Probleme selbst vornehmen sollte. Da es sich um eine Schleife mit festen Grenzen handelt, würde es mich sogar sehr wundern, wenn du hier unterschiede in der Umsetzung findest.

Was die Optimierung von Code angeht, so stehe ich dem immer sehr kritisch gegenüber. Es gibt zwar viele Stellen, an denen man den Code etwas perfomanter machen kann, aber es bleibt die Kosten/Nutzen Frage. Die Stellen zu finden und lesbar zu optimieren kostet häufig eine Menge Zeit (die man selten dafür hat).
Und dann? Wieviel Perfomance man wirklich rausholt hängt natürlich stark vom Programm ab. Aber es ist nun einmal so, dass zu 80% seiner Zeit ein Benutzer nur 20% der Funktionalität verwendet. Nur in diesen 20% machen also Optimierungen wirklich Sinn. Das Problem ist, dass dieser häufiger benutzte Teil erstmal gefunden werden muss. Zudem sollte insbesondere dieser Teil fehlerfrei bleiben (was dann gegen einzelne Optimierungen sprechen kann).

Es ist zwar wirklich interessant zu sehen was der Compiler wie umsetzt, aber auch hier gilt, dass die nächste Evolution des Compilers ein wenig besser optimieren wird und vielleicht einzelne eigene Optimierungen entfallen. Hat man leicht lesbare Quellen, hat man alle Vorteile, die das mit sich bringt. Mit einem neuen Compilieren sind die eventuellen perfomance Nachteile dann auch beseitigt... (zur Lesbarkeit gehören dann natürlich auch Prozeduren, auch wenn die einen minimalen Overhead mit sich bringen. Zudem haben sie einen großen Nutzen in Wiederverwendbarkeit)

So, gerade den roten Kasten bekommen, also sorry für die Widerholungen!

Go2EITS 9. Jul 2006 15:14

Re: Kleiner Optimierungstest
 
@jbg
@unwissende
Danke für die ausführlichen Antworten!

Dann doch Procedure/Funktionen mit lokalen Variablen, damit übersichtlich und besser wiederverwendbar,
der Rest ist Compilersache, der viel optimiert, wie ich in Euren Beiträgen entnehmen kann.
Und dann lege ich wieder ein for i=1 to Lenght(S) an. Damit wird der Code wieder übersichtlicher.

CU :thumb:
Go2EITS

Stefan Hueg 9. Jul 2006 15:40

Re: Kleiner Optimierungstest
 
Zu den globalen Variablen gibt es einen Grundsatz den mein GDI-Lehrer mal gesagt hat (studiere Informatik):
"Variablen sollten so nahe wie möglich an/in der Procedur deklariert werden..."

EDIT:
@jbg: Sollte es bei Strings bzw. dyn. Arrays nicht schneller sein (obwohl man dabei aufpassen muss was man mit den Parametern macht) wenn man die Parameter da per Referrenz übergibt, damit keine Kopie des Strings (der ja auch bis zu 2 GB groß sein kann) oder dyn. Arrays lokal angelegt wird?

jbg 9. Jul 2006 16:21

Re: Kleiner Optimierungstest
 
Zitat:

Zitat von Stefan Hueg
Sollte es bei Strings bzw. dyn. Arrays nicht schneller sein

Und was meinst du macht const? Übrigens Strings und dyn. Arrays werden immer ByRef übergeben und erst in der Methode kopiert bzw. unique gemacht.

Zitat:

wenn man die Parameter da per Referrenz übergibt
Bei var/out Parameters muss der Compiler trotzdem eine try/finally-Block einbauen und den Referenzzähler erhöhen, was er bei const eben nicht macht, weil da schon gar nicht die Möglichkeit besteht den Parameter zu ändern, weder direkt noch mit Hilfe einer aufgerufenen Funktion.

Stefan Hueg 9. Jul 2006 16:36

Re: Kleiner Optimierungstest
 
Mea Culpa :(

Da fällt mir grade eine Frage zum Thema Optimierung ein: Ist es besser

Delphi-Quellcode:
if Length(IrgendEinDynArray) > 0 then
...
oder

Delphi-Quellcode:
if high(IrgendEinDynArray) > -1 then
...
zu schreiben?

Dax 9. Jul 2006 16:42

Re: Kleiner Optimierungstest
 
Length ist "besser", da High erstens weniger intuitiv ist (High(leeres Array) = -1) und High intern auch nur Length aufruft, aber den Wert noch um 1 verkleinert, bevor es ihn zurückgibt.

Der_Unwissende 9. Jul 2006 17:38

Re: Kleiner Optimierungstest
 
Zitat:

Zitat von Dax
High intern auch nur Length aufruft, aber den Wert noch um 1 verkleinert, bevor es ihn zurückgibt.

Nun ja, ganz so stimmt das natürlich nicht. Wenn du die Länge möchtest, dann ist es natürlich sinnvoll, wenn du length verwendest. High = -1 macht schon deswegen keinen Sinn, weil es auch nicht 0 indizierte Arrays gibt (ein String ist z.B. 1 indiziert). Hier würden Low und High entsprechend die korrekte untere bzw obere Grenze liefern.

Delphi-Quellcode:
var x : Array[10..20] of Integer;
begin
  length(x) = 10

  // aber

  high(x) = 20 (Länge minus eins wäre vielmehr 9)
end;
Ein Vergleich welchen Wert high hat macht i.d.R. keinen Sinn. Ja, auch wenn man vieles auch anders schreiben kann, es ist nicht immer das Gleiche!

Dax 9. Jul 2006 17:52

Re: Kleiner Optimierungstest
 
Unwissender: Ich bezog mich direkt auf das Beispiel aus dem Post obendrüber, und da gings ja um dynamische Arrays :) Aber generell hast du Recht, Low() liefert den ersten Index und High() idR den letzten.


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