Benchmark FPC vs. others..

Für alles, was in den übrigen Lazarusthemen keinen Platz, aber mit Lazarus zutun hat.
Antworten
Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1671
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Benchmark FPC vs. others..

Beitrag von corpsman »

Servus zusammen,
getriggert durch diesen Thread im Forum. Habe ich mir mal den Spass gemacht und die "Vorarbeiten" geleistet.

Sprich, das Original Projekt geclont, und die playSimpel, sowie die play variante nach python3 portieren lassen (Chat GPT sei dank), des weiteren habe ich mal die C Version, wieder mittels Chat GPT nach FPC portieren lassen und erst mal alle Compilerfehler "repariert".

Das ganze läuft nun unter Linux Mint wie es soll und ist hier online verfügbar ;).

Lässt man das make benchmark laufen sieht FPC aber erst mal schlecht aus :/
corpsman@corpsman:/sda5/sda5/Temp/Score4_git$ make benchmark
make[1]: Entering directory '/sda5/sda5/Temp/Score4_git/C++'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/sda5/sda5/Temp/Score4_git/C++'
make[1]: Entering directory '/sda5/sda5/Temp/Score4_git/C'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/sda5/sda5/Temp/Score4_git/C'
make[1]: Entering directory '/sda5/sda5/Temp/Score4_git/FreePascal'
fpc -oscore4 score4.lpr
Free Pascal Compiler version 3.2.2+dfsg-32 [2024/01/05] for x86_64
Copyright (c) 1993-2021 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling score4.lpr
Linking score4
252 lines compiled, 0.0 sec
make[1]: Leaving directory '/sda5/sda5/Temp/Score4_git/FreePascal'
======================
= Running benchmarks =
======================
Benchmarking imperative memoized C++ ...: 0.030000
Benchmarking imperative C++ ...: 0.045000
Benchmarking imperative C ...: 0.045000
Benchmarking imperative FreePascal ...: 0.244000
corpsman@corpsman:/sda5/sda5/Temp/Score4_git$
Aber ich habe ja erst angefangen, nun kommt der Spaßige Teil, das ding "optimieren" und dem Rest der Welt zeigen das FPC kann was es verspricht ;).
Ihr alle seid herzlich eingeladen mit zu machen, Ideen zu geben oder einfach nur mit zu lesen *g*
--
Just try it

Benutzeravatar
Jorg3000
Lazarusforum e. V.
Beiträge: 417
Registriert: So 10. Okt 2021, 10:24
OS, Lazarus, FPC: Win64
Wohnort: NRW

Re: Benchmark FPC vs. others..

Beitrag von Jorg3000 »

Hi!
Ich habe gerade nur einen kurzen Blick in den Source geworfen, da sind mir zwei Sachen sofort ins Auge gestochen ...

Function IfThen(cond: Boolean; TrueVal, FalseVal: integer): integer;
... "if then" als Funktion auszulagern, ist für die Laufzeit ganz furchtbar. Wenn schon, dann möglichst als inline - das kann in Schleifen eine ganze Menge bringen.

Und bei dieser Doppelschleife ... wird die Adresse scores[y] vielleicht hunderte Male berechnet?

Code: Alles auswählen

// Horizontal spans
  For y := 0 To HEIGHT - 1 Do Begin
    score := scores[y][0] + scores[y][1] + scores[y][2];
    For x := 3 To WIDTH - 1 Do Begin
      score := score + scores[y][x];
      Inc(counters[score + 4]);
      score := score - scores[y][x - 3];
    End;
  End;
Lieber am Anfang der äußeren Schleife scoresYPointer:=@scores[y]; und dann im Folgenden immer scoresYPointer^[x] verwenden.
Grüße, Jörg

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1671
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Benchmark FPC vs. others..

Beitrag von corpsman »

*g*,
ich war noch damit beschäftigt ein Testsystem mit auf zu setzen, dass mir nun hilft ;)

Ifthen ist nu raus und deine Änderung:

Code: Alles auswählen

Function ScoreBoard(Const scores: TBoard): Integer;
Var
  counters: Array[0..8] Of Integer;
  x, y, score, idx: Integer;
  scoresYPointer: PInteger;
Begin
  FillChar(counters, SizeOf(counters), 0);

  // Horizontal spans
  For y := 0 To HEIGHT - 1 Do Begin
    score := scores[y][0] + scores[y][1] + scores[y][2];
    scoresYPointer := @scores[y];
    For x := 3 To WIDTH - 1 Do Begin
      score := score + scoresYPointer[x];
      Inc(counters[score + 4]);
      score := score - scoresYPointer[x - 3];
    End;
  End; 
Bringt uns zu:
Benchmarking imperative memoized C++ ...: 0.029000
Benchmarking imperative C++ ...: 0.045000
Benchmarking imperative C ...: 0.045000
Benchmarking imperative FreePascal ...: 0.232000
Also eine Verbesserung um 0.012, fehlen noch 0.203 auf C ;)
--
Just try it

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1671
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Benchmark FPC vs. others..

Beitrag von corpsman »

Sodale, mal ein klein wenig wibe coding betrieben..
Der code ist nicht mehr wieder zu erkennen, aber:
Benchmarking imperative memoized C++ ...: 0.029000
Benchmarking imperative C++ ...: 0.045000
Benchmarking imperative C ...: 0.045000
Benchmarking imperative FreePascal ...: 0.065000
Differenz C vs FPC runter auf 0.036 ;)

und das Testsystem ist auch Glücklich:
Run tests with:
Testcases: c_record.txt
Target app: ../FreePascal/score4
Ran 1374 tests
Ran 100 games

All tests passed.
--
Just try it

Warf
Beiträge: 2240
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Benchmark FPC vs. others..

Beitrag von Warf »

Solche Benchmarks sind zwar eine lustige Übung, im Sinne von wie schnell kann ich einen bestimmten Code bekommen, aber so Vergleiche hinken hinten und vorn:

Zum einen sind manche Sprachen nicht auf Performance ausgelegt. Zum Beispiel, Idiomatisches Pascal arbeitet extrem viel mit Klassen (deshalb "Object" Pascal). Klassen in Pascal haben aber massiven Overhead (schaut mal in die objpas.inc). Trotzdem ist es ganz normal das um z.B. um eine einfache Base64 Konvertierung zu machen mal eben 2 Klassen erzeugt werden, virtuelle Methoden aufgerufen werden und am ende wieder gefreed werden. Gewinnt man damit einen Blumentopf was Performance angeht? Nein... Ist das was schlechtes? In den allermeisten Fällen auch nein.
Object Pascal ist keine High Performance Sprache, das ist halt einfach so. Die meisten Anwendungen sind durch Externe Events limitiert. Eine GUI Anwnedung kann eh nicht schneller sein als der Nutzer klicken kann. Ein Server ist durch Netzwerk IO limitiert, etc. Dazu kommt das CPUs mit 4 GHz und 16 Kernen mit Hyperthreading und Predictive Execution so unfassbar schnell sind das es für das allermeiste einfach Schnell genug ist. Es hat einen Grund warum so viel Software heutzutage in Java, C#, Python, Javascript oder anderen VM basierten Sprachen geschrieben wird, obwohl sie ja eigentlich "langsam" sind. Weils einfach egal ist.

Das zweite ist halt das eine Benchmark nur so gut ist wie sie an Idiomatischem Code ist. In deinem Code sehe ich sehr viel Pointer Arithmetik und Rechnen mit Integer typen, aber kurioser weise keine Klassen, keine Vererbung, keinerlei String verarbeitung, etc. Wenn ich aber auf GitHub irgendwelche Open Source Pascal Projekte anschaue ist das aber genau anders rum. Was sagt dieser Benchmark über die tatsächliche Geschwindigkeit von Pascal Code im Feld aus?

Zum Schluss natürlich noch, verschiedene technologien haben verschiedene Stärken und schwächen. Java wird generell als langsamer als Pascal bezeichnet, das stimmt vermutlich im allgemeinen, aber je nachdem was man Testet kann das auch mal ganz anders sein. Vor einiger Zeit hab ich diesen Vergleich im Englischen Forum gepostet:

Code: Alles auswählen

public class Main {
 
    private final int adder;
 
    Main(int a) {
        adder = a;
    }
 
    int add(int i) {
        return i+adder;
    }
 
    public static void main(String args[]) {
        long startTime = System.currentTimeMillis();
        int accum = 0;
        for (long i=0;i<1000000000l; ++i) {
            Main m = new Main((int)i);
            accum=m.add(accum);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("Total execution time: " + (endTime-startTime) + "ms");
    }
}
Als Pascal Code:

Code: Alles auswählen

program Project1;
 
{$mode objfpc}{$H+}
uses SysUtils;
 
type
  TTest = class
  private
    adder: Integer;
  public
    function Add(i: Integer): Integer;
    constructor Create(a: Integer);
  end;
 
function TTest.Add(i: Integer): Integer;
begin
  Result:=i+adder;
end;
Mit dem folgenden Ergebnis:

Code: Alles auswählen

    Java:         Total execution time: 200ms
    Pascal (-O3): Total execution time: 22657ms 
Java ist um einen Faktor 100 schneller als der FPC. Bedeutet das das Java die schnellere Sprache ist? Nein es bedeutet nur das das erstellen von Temporären Objekten in Java quasi kostenlos ist, während das erstellen von Klassen in Pascal extrem aufwendig ist.
C und C++ Compiler haben extrem gute Optimierungen, zum einen weil viel Arbeit in die Compiler reinfließt, zum anderen weil die Sprachen dafür gebaut das der Code möglichst viele Infos zum optimieren bereitstellen kann (z.B. über das Typsystem mit sachen wie restricted, const, etc.). Du kannst z.B. auch mit dem FPC das LLVM backend benutzen und hast einen großen Teil der Optimierungen die C und C++ haben damit auch in Pascal. Es wird dennoch ein bisschen was Fehlen weil C und C++ einfach von Sprachdesign her so gebaut sind schneller zu sein

Langer Rede kurzer Sinn: Auf so Benchmarks sollte man eh nicht zu viel geben. Am ende Zählt nur eins: Ist das Werkzeug was ich nutze das richtige für den Job. Mit hinsicht auf Performance: Erreicht FPC die notwendige Performance für ein bestimmtes Projekt, wenn ja, ist egal ob was anderes schneller ist.

Mathias
Beiträge: 7131
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Benchmark FPC vs. others..

Beitrag von Mathias »

Java ist um einen Faktor 100 schneller als der FPC. Bedeutet das das Java die schnellere Sprache ist? Nein es bedeutet nur das das erstellen von Temporären Objekten in Java quasi kostenlos ist, während das erstellen von Klassen in Pascal extrem aufwendig ist.
Du sagst, das die Verwaltung von FPC-Klassen recht aufwendig ist.
Da wäre noch ein spanneder Vergleich, wen man etwas auf dem Handle-Prinzip aufbaut. So wie es die meisten clibs machen. Gutes Beispiel SDL. Oder wen es sehr komplex ist, die ganze glib.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1671
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Benchmark FPC vs. others..

Beitrag von corpsman »

*g*,
@warf, Ich stimme dir zu und mir ist das sehr wohl bewusst, ich versuche solche und andere Themen ja auch in meinem Research Repository zu betrachten.

Aber wenn ich so ein Projekt sehe, wo man alle möglichen verrückten Sprachen einbaut und fleißig vergleicht und dann FPC so einfach mit auf die Liste genommen werden kann, dann bin ich da mit dabei ;). Im Idealfall kommt a bissl Werbung für FPC bei raus, und im schlechtesten Fall hab ich 3h umsonst gecoded (da ich aber irgendwie immer was dabei lerne, und sei es nur neue Sichtweisen auf Implementierungen und wie erschreckend Gut ChatGPT das mit meiner Hilfe hin bekommen hat)
--
Just try it

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1671
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Benchmark FPC vs. others..

Beitrag von corpsman »

So, hab es nun geschafft das Treepruning mit rein zu bekommen:
======================
= Running benchmarks =
======================
Benchmarking imperative memoized C++ ...: 0.029000
Benchmarking imperative C++ ...: 0.045000
Benchmarking imperative C ...: 0.045000
Benchmarking imperative FreePascal ...: 0.011000
Das Testsystem ist immer noch Glücklich:
Run tests with:
Testcases: c_record.txt
Target app: ../FreePascal/score4
Ran 1363 tests
Ran 100 games

All tests passed.
Keine Ahnung warum die anderen Varianten das pruning nicht aktiviert haben, ohne Pruning sieht das Ergebnis so aus:
======================
= Running benchmarks =
======================
Benchmarking imperative memoized C++ ...: 0.029000
Benchmarking imperative C++ ...: 0.045000
Benchmarking imperative C ...: 0.045000
Benchmarking imperative FreePascal ...: 0.063000
Damit ist eigentlich klar, egal welche Programmiersprache, Algorithmisch hohlt man immer noch am meisten, aber das wussten wir ja alle bereits ;)
--
Just try it

MmVisual
Beiträge: 1620
Registriert: Fr 10. Okt 2008, 23:54
OS, Lazarus, FPC: Winuxarm (L 4.2 FPC 3.2.2)
CPU-Target: 32/64Bit

Re: Benchmark FPC vs. others..

Beitrag von MmVisual »

Jetzt mache bitte noch diesen Test:

Füge am Ende von deiner Main Form eine Ressource hinzu:

Code: Alles auswählen

initialization
{$I irgendwas.lrs} 
Mache eine ZIP Datei mit einigen Dateien drin, einer Größe von ca. 10MB und mache aus der ZIP eine Ressource und füge diese Ressource am Ende der Main-Form hinzu.

Und dann übersetze mal und vergleiche die Compile Zeit mit und ohne diese Ressource.

Eigentlich sollte man meinen dass ist ja nur ertwas was einfach der EXE mit rein gelinkt wird, tatsächlich braucht nur wegen dieser kleinen Zeile das Lazarus bei meinem Rechner 30 Sekunden länger. :shock:
EleLa - Elektronik Lagerverwaltung - www.elela.de

Warf
Beiträge: 2240
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Benchmark FPC vs. others..

Beitrag von Warf »

Mathias hat geschrieben: So 23. Nov 2025, 08:45 Du sagst, das die Verwaltung von FPC-Klassen recht aufwendig ist.
Da wäre noch ein spanneder Vergleich, wen man etwas auf dem Handle-Prinzip aufbaut. So wie es die meisten clibs machen. Gutes Beispiel SDL. Oder wen es sehr komplex ist, die ganze glib.
Naja ich meine damit explizit den Overhead von Klassen, nehmen wir mal das Beispiel as is:

Code: Alles auswählen

program Project1;
 
{$mode objfpc}{$H+}
uses SysUtils;
 
type
  TTest = class
  private
    adder: Integer;
  public
    function Add(i: Integer): Integer;
    constructor Create(a: Integer);
  end;
 
function TTest.Add(i: Integer): Integer;
begin
  Result:=i+adder;
end;
 
constructor TTest.Create(a: Integer);
begin
  adder:=a;
end;
 
var
  i: Int64;
  accum: Integer;
  start: QWord;
  t: TTest;
begin
  start:=GetTickCount64;
  accum:=0;
  i:=0;
  while i<1000000000 do
  begin
    t:=TTest.Create(Integer(i));
    accum:=t.Add(accum);
    t.Free;
    Inc(i);
  end;
  WriteLn('Total execution time: ', GetTickCount64-start, 'ms');
end.
Auf -O3 auf dem Rechner an dem ich grade sitze (x64 Linux):

Code: Alles auswählen

Total execution time: 17097ms
Jetzt änder ich mal Klassen zu Objects:

Code: Alles auswählen

program Project1;

{$mode objfpc}{$H+}
uses SysUtils;

type
  PTest = ^TTest;
  TTest = object
  private
    adder: Integer;
  public
    function Add(i: Integer): Integer;
    constructor Create(a: Integer);
    destructor Final;// Damit der Finalization Code ausgeführt werden kann
  end;
// ...

destructor TTest.Final; // Damit der Finalization Code ausgeführt werden kann
begin

end;

var
  i: Int64;
  accum: Integer;
  start: QWord;
  t: PTest; // Nutzung von expliziten Pointern
begin
  start:=GetTickCount64;
  accum:=0;
  i:=0;
  while i<1000000000 do
  begin
    new(t, Create(Integer(i))); // Object spezifische Syntax
    accum:=t^.Add(accum);
    dispose(t, Final);
    Inc(i);
  end;
  WriteLn('Total execution time: ', GetTickCount64-start, 'ms');
end.
Selbes Featureset genutzt wie in Klassen, nur ein paar syntaktische Änderungen, Ergebnis:

Code: Alles auswählen

Total execution time: 12808ms
Nur durch diese kleine Änderung ein Performance bonus von 40%.

Als nächstes wechseln wir mal auf Advanced Records:

Code: Alles auswählen

program Project1;

{$mode objfpc}{$H+}
{$ModeSwitch advancedrecords}
uses SysUtils;

type
  PTest = ^TTest;
  TTest = record
  private
    adder: Integer;
  public
    function Add(i: Integer): Integer;
    procedure Init(a: Integer); // Kein Konstruktor mehr sondern simple Prozedur
  end;
// ...
procedure TTest.Init(a: Integer);
begin
  adder:=a;
end;

// ...
  while i<1000000000 do
  begin
    new(t); // Keine Objektsyntax mehr
    t^.Init(Integer(i));
    accum:=t^.Add(accum);
    dispose(t);
    Inc(i);
  end;
  WriteLn('Total execution time: ', GetTickCount64-start, 'ms');
end.
Semantisch exakt das selbe wie oben, jetzt:

Code: Alles auswählen

Total execution time: 6420ms
Jetzt ist es um 80% schneller als die Objects und einen Faktor 2.6 schneller als die Klasse.

Und jetzt benutzen wir gar keine Pointer/Heap Allokationen sondern machen alles auf dem Stack:

Code: Alles auswählen

// ...
var
  i: Int64;
  accum: Integer;
  start: QWord;
  t: TTest; // Kein Pointer
//...
    t.Init(Integer(i)); // Keine Pointer mehr
    accum:=t.Add(accum);
    Inc(i);
//...

Code: Alles auswählen

Total execution time: 1458ms
Nochmal einen Faktor 5,3 schneller und damit zu den Ursprünglichen Klassen ein Faktor 12.
Zum schluss noch inlining:

Code: Alles auswählen

    function Add(i: Integer): Integer; inline;
    procedure Init(a: Integer); inline;
Und wir bekommen:

Code: Alles auswählen

Total execution time: 383ms
Und wir sind nochmal einen Faktor 4 schneller und damit einen Faktor 45 schneller als mit Klassen. Und sind fast so schnell wie Java, in einem Beispiel in dem Javas JIT Optimalbedingungen hat.

Und an keiner Stelle habe ich was an der Semantik oder Algorithmik des Programms geändert, nur indem ich ein Sprachfeature durch Äquivalente andere austausche bekommt man 10x-50x speedup hin

C und C++ Structs und Klassen sind nochmal etwas effizienter als Advanced records, da C keine Managed typen hat und C++ im gegensatz zu Pascal die Initialisierung und Finalisierung nicht mit RTTI umsetzt und damit besser inlined.

Wenn du also handle zu C oder C++ Objekten verwendest, werden die vermutlich etwas schneller sein als die Pointer basierten Records oben.

Antworten