Gravierende Laufzeitunterschiede

Für allgemeine Fragen zur Programmierung, welche nicht! direkt mit Lazarus zu tun haben.

Gravierende Laufzeitunterschiede

Beitragvon Adrian » 26. Sep 2018, 21:02 Gravierende Laufzeitunterschiede

Servus!

Kann mir jemand mal auf die Sprünge helfen.
Vor einigen Jahren schrieb ich ein kleines Programm zur Pi-Annäherung ungefähr so, wie es der nachfolgende Ausschnitt zeigt:
Code: Alles auswählen
 
var
  i : LongWord;
  x, n : Real;
  QueryLos, QueryHalt : Int64;
//irgendwas ...
  n:=1;
  x:=0;
  QueryLos := GetTickCount64;
  for i:=1 to 500000000 do
  begin
    x:=x+(1/n);
    if n<0 then n:=n-2;
    if n>0 then n:=n+2;
    n:=(-1)*n;
  end;
  QueryHalt := GetTickCount64;
  Dauer:= IntToStr(QueryHalt – QueryLos);
end;
 

Nur so interessehalber führte ich das Programm auf zwei recht verschiedenen Rechnern durch:
System 1: Netbook / Atom-Prozessor N450 / 1,66 GHz / 2 GB RAM
System 2: Desktop / Intel i5-2320 / 3 GHz / 8 GB RAM
Die Laufzeiten waren doch recht unterschiedlich, bei System 1 etwa 42 Sekunden, bei System 2 nur knapp 4 Sekunden. Und das ists, was ich nicht ganz nachvollziehen kann.
Da bei dem Programm nur ganz wenig Speicher gebraucht wird und auch kaum Festplattenzugriffe erfolgen, da sollte meiner Meinung nach nur die Taktfrequenz ausschlaggebend sein. Das Ergebnis widerspricht aber offensichtlich meiner Meinung.

Und nun suche ich jemanden, der mir erklären kann, wo mein Denkfehler ist, und warum so gravierende Laufzeitunterschiede auftreten.

Gruß,
Adrian
Adrian
 
Beiträge: 25
Registriert: 12. Nov 2007, 12:41
OS, Lazarus, FPC: Winux (L 1.6 FPC 3.0.0) | 
CPU-Target: 32Bit
Nach oben

Beitragvon Mathias » 26. Sep 2018, 21:18 Re: Gravierende Laufzeitunterschiede

Ein i5 arbeitet einiges schneller als ein Atom. Ein Atom kann man fast mit einem Rasberry Vergleichen.
Es tönt zwar komisch, wieso verbratet ein i6 einiges meher Strom als ein Atom ?
Mit Lazarus sehe ich gün
Mit Java und C/C++ sehe ich rot
Mathias
 
Beiträge: 4327
Registriert: 2. Jan 2014, 17:21
Wohnort: Schweiz
OS, Lazarus, FPC: Linux (die neusten Trunc) | 
CPU-Target: 64Bit
Nach oben

Beitragvon indianer-frank » 26. Sep 2018, 21:22 Re: Gravierende Laufzeitunterschiede

Was soll (-1)*n bei einem Longword bedeuten? Und n kann selbst verständlich nicht kleiner als 0 sein. Sehe gerade n ist unüblicherweise auch real.

Und bist Du sicher, daß die Real-Arithmetik gleich implementiert ist? Beim i5 mit ziemlicher Sicherheit als Hardware double. Wie sieht das bei Atom aus. Wenn da Softfloat zum Einsatz kommt, wäre das ein weiterer Sargnagel.
indianer-frank
 
Beiträge: 133
Registriert: 30. Nov 2008, 21:53

Beitragvon Mathias » 26. Sep 2018, 22:09 Re: Gravierende Laufzeitunterschiede

Wenn da Softfloat zum Einsatz kommt, wäre das ein weiterer Sargnagel.
Meinst du, sowas gibt es auf einem Intel noch, ab eine 486DX, wurde der Matheprozessor fest verbaut.
Mit Lazarus sehe ich gün
Mit Java und C/C++ sehe ich rot
Mathias
 
Beiträge: 4327
Registriert: 2. Jan 2014, 17:21
Wohnort: Schweiz
OS, Lazarus, FPC: Linux (die neusten Trunc) | 
CPU-Target: 64Bit
Nach oben

Beitragvon theo » 26. Sep 2018, 23:04 Re: Gravierende Laufzeitunterschiede

Einfach mal die Benchmarks vergleichen:

https://www.cpubenchmark.net/compare/In ... 0/790vs618
theo
 
Beiträge: 8058
Registriert: 11. Sep 2006, 18:01

Beitragvon Warf » 27. Sep 2018, 02:42 Re: Gravierende Laufzeitunterschiede

Branch prediction, Cache, erweiterter Befehlssatz, Fast memory access, DDR3 VS DDR2. Da kommt ne menge zusammen

Das ist als würdest du einen Opel Corsa mit einem BMW M5 vergleichen. Der Atom kann einfach viel weniger (als Mobile CPU soll er ja auch keine powermaschine sein, lieber weniger können dafür weniger Strom brauchen).

Was bei dir den Riesen unterschied macht keine Ahnung, aber wenn die Variablen nicht in Registern liegen kann allein der Unterschied im Memory Access dafür verantwortlich sein
Warf
 
Beiträge: 984
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: Mac OSX 10.11 | Win 10 | FPC 3.0.0 | L trunk | 
CPU-Target: x86_64, i368, ARM
Nach oben

Beitragvon Horst_h » 27. Sep 2018, 12:49 Re: Gravierende Laufzeitunterschiede

Hallo,

wie der CPu Bench zeigt, liegt die single-thread performance bei 1:8 also nicht so weit weg von 1:10 hier.
Man kann aber das Programm ein wenig umstellen, um ohne IF's und n als double auszukommen.
Vielleicht hilft das ja ein wenig.

Code: Alles auswählen
program p;
uses
  sysutils;
 
var
  x : double;
  i : NativeInt;
  n : NativeUint;
  QueryLos, QueryHalt : Int64;
begin
//irgendwas ...
  n:=1;
  x:=0;
  QueryLos := GetTickCount64;
  for i:=1 to 250000000 do
  begin
    x:=x+(1/n);
    n:=n+2;
    x:=x-(1/n);
    n:=n+2;
  end;
  QueryHalt := GetTickCount64;
  writeln(QueryHalt-QueryLos);
  writeln(x*4.0-pi);
end.
 

Das ergibt auf einem Ryzen 5 1600
Code: Alles auswählen
1922
-2.00053541549345681450E-0009

Die urspüngliche Version der Schleife brauchte bei mir knapp 4 Sekunden.

Gruß Horst
Horst_h
 
Beiträge: 66
Registriert: 20. Mär 2013, 08:57

Beitragvon sstvmaster » 27. Sep 2018, 13:22 Re: Gravierende Laufzeitunterschiede

@Horst

Intel Core2Duo E8500 @ 3.16GHz
Code: Alles auswählen
7238
-2.00038753378657674364E-0009
OS: Windows 7 32/64bit
Lazarus 1.8.4, 32bit
Lazarus 2.1.0 Trunk 3.3.1, 32bit
sstvmaster
 
Beiträge: 87
Registriert: 22. Okt 2016, 22:12
OS, Lazarus, FPC: Lazarus 1.8.4 + 2.1.0 Trunk 3.3.1 / Win32, Windows 7 32+64bit | 
CPU-Target: 32Bit
Nach oben

Beitragvon Timm Thaler » 27. Sep 2018, 18:53 Re: Gravierende Laufzeitunterschiede

i5-4570 @3.20GHz

Optimierung -O1, mit Debugger

Code: Alles auswählen
3916
-2.0005352929786113E-009


Optimierung -O3, ohne Debugger

Code: Alles auswählen
3775
-2.0005352929786113E-009
Timm Thaler
 
Beiträge: 707
Registriert: 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded | 
CPU-Target: Raspberry Pi 3
Nach oben

Beitragvon Adrian » 6. Okt 2018, 18:10 Re: Gravierende Laufzeitunterschiede

Hallo Horst!

Dein Programm verbessert die Laufzeit natürlich, aber es ging mir eigentlich nicht um die Optimierung, sondern um die Verarbeitungsgeschwindigkeit der einzelnen CPUs.
Da während meines Studiums noch der 8085 durchgenommen wurde (damals, als es noch den guten Westen und den bösen Osten gab, als die Mauer noch stand und das Geld noch was wert war), kenne ich mich mit den modernen Prozessorarchitekturen nicht gut aus. Aber das würde ich ganz gern ändern. Wer kann mir den ein gutes Buch nennen (darf ruhig auch in Englisch sein), in dem ich so etwas nachschlagen kann? Also beispielsweise Beschreibungen, welche Befehle wieviele Taktzyklen brauchen. Gerade Studenten der Elektrotechnik müssten das doch wissen.

Gruß,
Adrian
Adrian
 
Beiträge: 25
Registriert: 12. Nov 2007, 12:41
OS, Lazarus, FPC: Winux (L 1.6 FPC 3.0.0) | 
CPU-Target: 32Bit
Nach oben

Beitragvon wp_xyz » 6. Okt 2018, 19:00 Re: Gravierende Laufzeitunterschiede

Horst_h hat geschrieben:Das ergibt auf einem Ryzen 5 1600
Code: Alles auswählen
1922
-2.00053541549345681450E-0009

Die urspüngliche Version der Schleife brauchte bei mir knapp 4 Sekunden.

Seltsam, das kann ich überhaupt nicht bestätigen. Auf meinem (etwas betagten) i5 ist Adrian's Code fast doppelt so schnell als Horst_h's. Erst wenn ich in Horst's Code die NativeUInt gegen stinknormale Integer austausche, wird dieser Code genauso schnell wie bei Adrian. Die Floating-Point-Rechnung macht sich nicht bemerkbar. Hätte ich nicht erwartet. Also Vorsicht bei vermeintlich naheliegenden Optimierungen!

Code: Alles auswählen
program p;
uses
  sysutils;
 
const
  IMAX = 250000000;
var
  x: double;
  i : NativeInt;
  n : NativeUInt;
  ni: Integer;
  nd: Double;
  QueryLos, QueryHalt : Int64;
 
begin
  WriteLn('Adrian / double');
  nd:=1;
  x := 0;
  QueryLos := GetTickCount64;
  for i:=1 to IMAX*2 do
  begin
    x := x + (1/nd);
    if nd<0 then nd:=nd-2;
    if nd>0 then nd:=nd+2;
    nd := -nd;
  end;
  QueryHalt := GetTickCount64;
  writeln(QueryHalt-QueryLos);
  writeln(x*4.0-pi);
  WriteLn;
 
  WriteLn('Horst_h / NativeUInt');
  n:=1;
  x:=0;
  QueryLos := GetTickCount64;
  for i:=1 to IMAX do
  begin
    x:=x+(1/n);
    inc(n, 2);
    x:=x-(1/n);
    inc(n, 2);
  end;
  QueryHalt := GetTickCount64;
  writeln(QueryHalt-QueryLos);
  writeln(x*4.0-pi);
  WriteLn;
 
  WriteLn('Horst_h / integer');
  ni:=1;
  x:=0;
  QueryLos := GetTickCount64;
  for i:=1 to IMAX do
  begin
    x:=x+(1/ni);
    inc(ni, 2);
    x:=x-(1/ni);
    inc(ni, 2);
  end;
  QueryHalt := GetTickCount64;
  writeln(QueryHalt-QueryLos);
  writeln(x*4.0-pi);
  WriteLn;
 
  WriteLn('Horst_h / double');
  nd:=1;
  x:=0;
  QueryLos := GetTickCount64;
  for i:=1 to IMAX do
  begin
    x:=x+(1.0/nd);
    nd:=nd+2;
    x:=x-(1.0/nd);
    nd:=nd+2;
  end;
  QueryHalt := GetTickCount64;
  writeln(QueryHalt-QueryLos);
  writeln(x*4.0-pi);
  WriteLn;
 
  WriteLn('ENTER druecken zum Beenden...');
  ReadLn;
end.   


Mit Standard-Optimierung -O1 (Unterschiede zu ohne Optimierung, -O2, -O3 und zu -O4 sind < 1%):
Adrian / double
3375
-2.00038753378657674364E-0009

Horst_h / NativeUInt
7406
-2.00038753378657674364E-0009

Horst_h / integer
3375
-2.00038753378657674364E-0009

Horst_h / double
3360
-2.00038753378657674364E-0009

ENTER druecken zum Beenden...
wp_xyz
 
Beiträge: 2651
Registriert: 8. Apr 2011, 08:01

Beitragvon Horst_h » 8. Okt 2018, 12:43 Re: Gravierende Laufzeitunterschiede

Hallo,

besonders bei 32-Bit bekommt man die geringe Zahl an Registern zu spüren.Das auslagern in kleine Funktionen bringt erheblich was und es sollte auch vergleichbarer werden, egal wo man die Funktion im Quelltext benutzt.

Code: Alles auswählen
program p;
uses
  sysutils;
 
const
  IMAX = 250000000;
var
  x: double;
  QueryLos, QueryHalt : Int64;
 
function GetPiAdrian:double;
var
  x,n : double;
  i : NativeInt;
begin
  n:=1;
  x := 0;
  for i:=2*Imax-1 downto 0 do
  begin
    x := x + (1/n);
   // leicht geändert und einen Vergleich gespart, denn n= 0 kommt nicht vor
    if n>0 then
      n:= -(n+2)
    else
      n:= -(n-2);
  end;
  GetPiAdrian := x;
end;
 
function GetPiNativeInt:double;
var
  x : double;
  n : NativeInt;
  i : NativeInt;
begin
  n:=1;
  x := 0;
  for i:=1 to IMAX do
  begin
    x:=x+(1/n);
    inc(n, 2);
    x:=x-(1/n);
    inc(n, 2);
  end;
  GetPiNativeInt := x;
end;
 
function GetPiLongInt:double;
var
  x : double;
  n : LongInt;
  i : NativeInt;
begin
  n:=1;
  x := 0;
  for i:=1 to IMAX do
  begin
    x:=x+(1/n);
    inc(n, 2);
    x:=x-(1/n);
    inc(n, 2);
  end;
  GetPiLongInt := x;
end;
 
function GetPiDouble:double;
var
  x,n : double;
  i : NativeInt;
begin
  n:=1;
  x := 0;
  for i:=1 to IMAX do
  begin
    x:=x+(1.0/n);
    n:=n+2;
    x:=x-(1.0/n);
    n:=n+2;
  end;
  GetPiDouble:= x;
end;
 
begin
  WriteLn('Adrian / double');
  QueryLos := GetTickCount64;
  x := GetPiAdrian;
  QueryHalt := GetTickCount64;
  writeln(QueryHalt-QueryLos); writeln(x*4.0-pi);WriteLn;
 
  WriteLn('Horst_h / NativeUInt');
  QueryLos := GetTickCount64;
  x := GetPiNativeInt;
  QueryHalt := GetTickCount64;
  writeln(QueryHalt-QueryLos);writeln(x*4.0-pi);WriteLn;
 
  WriteLn('Horst_h / integer');
  QueryLos := GetTickCount64;
  x := GetPiLongInt;
  QueryHalt := GetTickCount64;
  writeln(QueryHalt-QueryLos);writeln(x*4.0-pi);WriteLn;
 
  WriteLn('Horst_h / double');
  QueryLos := GetTickCount64;
  x:= GetPiDouble;
  QueryHalt := GetTickCount64;
  writeln(QueryHalt-QueryLos);writeln(x*4.0-pi);WriteLn;
 
  WriteLn('ENTER druecken zum Beenden...');
  ReadLn;
end.

Linux64
Ryzen 5 1600 @ 3.2 Ghz ( Turbo? aus )

32-Bit Kompilat fpc -O1 -al -Xs "%f" // Benutzt die FPU und schreibt sichert ständig die Daten auf den Stack.
Code: Alles auswählen
 Adrian / double
2727
-2.00038753378657674364E-0009
 
Horst_h / NativeUInt
2318
-2.00038753378657674364E-0009
 
Horst_h / integer
2318
-2.00038753378657674364E-0009
 
Horst_h / double
2407
-2.00038753378657674364E-0009
 
ENTER druecken zum Beenden...

64-Bit Kompilat
/usr/lib/fpc/3.0.4/ppcx64 -O1 -al -Xs "%f"
Code: Alles auswählen
 
Adrian / double
1916
-2.00053541549345681450E-0009
 
Horst_h / NativeUInt
1662
-2.00053541549345681450E-0009
 
Horst_h / integer
1659
-2.00053541549345681450E-0009
 
Horst_h / double
1707
-2.00053541549345681450E-0009
 
ENTER druecken zum Beenden...

Jetzt die Optimierung hochgeschraubt, die bei 32-Bit fast nichts brachte.
/usr/lib/fpc/3.0.4/ppcx64 -O3 -al -Xs "%f"
Code: Alles auswählen
 
Adrian / double
657
-2.00053541549345681450E-0009
 
Horst_h / NativeUInt
615
-2.00053541549345681450E-0009
 
Horst_h / integer
616
-2.00053541549345681450E-0009
 
Horst_h / double
612
-2.00053541549345681450E-0009
 
ENTER druecken zum Beenden...


Man sieht, dass die Berechnung mit der FPU ( 32-Bit ) leicht andere Zahlen als mit SSE ( 64-Bit ) liefert.

Gruß Horst
EDIT:
es 500e6 Berechnungen.
Takte pro Berechnugen = Zeit/AnzahlBerechnungen * CPU-Takte

612 ms/500e6*3,2e6 Takte/ms = 3,917 Takte/Berechnung selbst bei Turbo mit 3,6 Ghz sind es nur 4,4 Takte.
Horst_h
 
Beiträge: 66
Registriert: 20. Mär 2013, 08:57

• Themenende •

Zurück zu Programmierung



Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast

porpoises-institution
accuracy-worried