Entwickler-Ecke

Programmierwerkzeuge - Lazarus vs. Delphi: Geschwindigkeit


Gausi - Sa 12.04.08 15:23
Titel: Lazarus vs. Delphi: Geschwindigkeit
Ich habe gerade ein kleines Delphi-Projekt nach Lazarus konvertiert. Die automatische Konvertierung hat nicht so ganz geklappt, daher hat das etwas länger gedauert. Aber jetzt läuft es so halbwegs.

Aber: Es läuft deutlich langsamer. Da steckt ein Faktor zwischen 2 und 3 drin. Hat jemand Erfahrung damit, kann das bestätigen oder weiß, ob man da was drehen kann? Gibt es da Compiler-Optionen, die das verursachen können, oder ist der Freepascal-Compiler einfach nur deutlich schlechter als der von Delphi?


Jakob_Ullmann - Sa 12.04.08 16:20

1. Warum konvertierst du das Projekt denn nach Lazarus?
2. Ich denke, es wird schon seine Gründe haben, weshalb Delphi einen (hohen) Preis hat und Lazarus / FreePascal kostenlos ist.


Dunkel - Sa 12.04.08 17:01

user profile iconJakob_Ullmann hat folgendes geschrieben:
1. Warum konvertierst du das Projekt denn nach Lazarus?

Hast Du schon mal probiert eine Delphi-Echse unter Linux auszuführen? Oder unter Mac OS, oder auf einem PPC, oder, oder oder...

user profile iconJakob_Ullmann hat folgendes geschrieben:

2. Ich denke, es wird schon seine Gründe haben, weshalb Delphi einen (hohen) Preis hat und Lazarus / FreePascal kostenlos ist.

Nicht alles, was Open-Source oder Freeware ist, ist zwangsläufig schlechter als das kommerzielle Pendent. :roll:

BTT:
Keine Ahnung. Ich habe vor Urzeiten auch mal versucht, ein Projekt nach Lazarus/Free Pascal zu portieren. Hat ebenfalls nicht wirklich geklappt. Außerdem hat mir die IDE nicht wirklich gefallen; also habe ich es gelassen. :?


Allesquarks - Sa 12.04.08 19:31

In den Projektoptionen gab es meiner Meinung nach einen Schalter für Optimierungen. Also ich hab auch schon andere Meinungen gehört, dass freepascal wesentlich schneller ist, was ich mir durchaus eher vorstellen kann, da ich jedesmal nen Krampf bekomme, wenn ich das Delphi CPU Window öffne.
Vielleicht liegt das an deren LCL oder wie sie es nennen. Die programmieren ja immer noch die VCL nach. Könnte mir vorstellen, dass das noch nicht so läuft bzw die erstmal Masse statt Klasse schaffen wollen.
Vlt hilft es ja auch Teile davon mit dem gcc zu compilieren. Da soll ja auch ein pascal-part drin sein. Das ist aber nur eine Anregung ich jedenfalls weiß über das speziell gar nichts.


Horst_H - Sa 12.04.08 20:31

Hallo,

Eine Konsolenanwendung hat aber kein geändertes Verhalen ( ausser der gigantischen Grösse 1,6 Mb..6,3 Mb (Zeileneinformationen des debuggers an ))
http://www.delphi-forum.de/viewtopic.php?p=452360

Turbo Delphi 2007
8 bit lookup table (Reference): 1034435
16 bit lookup table: 530772
Horst_H: 541955 <--
Horst_H2: 220742
Reinhard Kern: 1692761

Freepascal 2.2.0
optimiert Register variable Optimierugen O2 und für AMD eingestellt, aller debugger Kram raus.
8 bit lookup table (Reference): 1381376
16 bit lookup table: 653001
Horst_H: 248621 <--
Horst_H2: 235787
Reinhard Kern: 1354820

Lazarus
optimiert für AMD nicht eingestellt
8 bit lookup table (Reference): 1094116
16 bit lookup table: 596681
Horst_H: 399932 <--
Horst_H2: 226541
Reinhard Kern: 1281640

Ohne die Registeroptimierung ist es aber viel langsamer

Gruß Horst


Gausi - Sa 12.04.08 20:40

Hm. In der Tat. Jetzt ist es schneller. Aber ich glaube das liegt daran, dass einfach ganze Funktionen wegoptimiert wurden, als ich {$mode delphi} auf {$mode objfpc} gesetzt habe. Also alle Funktionen bis auf die, die ca. 600mal so lange wie unter Delphi braucht. Der Aufbau einer bestimmten Datenstruktur braucht auf einmal ein paar Zehnerpotenzen mehr Zeit. :gruebel:

Ich fürchte, da geht irgendwas komplett daneben. :(


Gausi - So 13.04.08 13:28

Ok, das Problem mit dem mode ist geklärt - da wurden Strings als Pascal-Strings und nicht mehr als Ansi-Strings interpretiert.

Trotz aller Optimierungen in den Compiler-Einstellungen des Projekts bleibt das ganze langsamer. Besonders extrem ist das bei dieser Funktion (ist mal egal, was die macht. Die macht halt was ;-))


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
  // Datenstruktur
  TDawgRecord = record
      Init: Integer;  // Der Startknoten
      Len: Array of Integer;        // Speichert die "Länge" der Knoten
      Terminal: Array of Boolean;   // ... die Eigenschaft Terminal  der Knoten
      SuffixLink: Array of Integer; // ... die Suffixlinks der Knoten
      Target:  Array of Array[Char] of Integer;  // Kanten, Größe: |Nodes| * 256
  end;   

function BuildDawg(p: AnsiString): TDawgRecord;
var m,i,NodeCount: Integer;
  last, u, v, w, r: Integer;
  c:Char;
  CopyC: Char;
begin
  m := length(p);
  Setlength(result.Len, 2*m+2);
  Setlength(result.Terminal, 2*m+2);
  Setlength(result.SuffixLink, 2*m+2);
  Setlength(result.Target, 2*m+2);
  result.Init := 0;
  result.SuffixLink[0] := -1;
  for c := Low(Char) to High(Char) do result.Target[0,c] := -1;
  NodeCount := 0;
  last := 0;

  for i := 1 to m do
  begin
      u := last;
      inc(NodeCount); // neuen Knoten einfügen
      v := NodeCount;
      result.Len[v] := result.Len[u] + 1;
      // Knoten initialisieren
      result.Terminal[v] := False;
      for c := Low(Char) to High(Char) do result.Target[v,c] := -1;

      While (u > 0and (result.Target[u,p[i]] = -1do
      begin
          // Kante einfügen
          result.Target[u,p[i]] := v;
          u := result.SuffixLink[u];
      end;

      if result.Target[u,p[i]] = -1  then
      begin
          // Wir sind am Startknoten
          result.Target[0,p[i]] := v;
          result.SuffixLink[v] := 0;
      end else
      begin
          // Kante existiert
          w := result.Target[u,p[i]];
          if result.Len[u] + 1 = result.Len[w] then
          begin
              // (u,w) ist feste Kante
              result.SuffixLink[v] := w;
          end else
          begin
              inc(NodeCount);
              r := NodeCount;
              result.Terminal[r] := False;

              //  Knoten w kopieren
              for CopyC := Low(Char) to High(Char) do
                result.Target[r,CopyC] := result.Target[w,CopyC];
              result.SuffixLink[r] := result.SuffixLink[w];

              result.Len[r] := result.Len[u] + 1;
              result.SuffixLink[w] := r;
              result.SuffixLink[v] := r;

              while (u >= 0and (result.Target[u,p[i]] = w) do
              begin
                  result.Target[u,p[i]] := r;
                  u := result.SuffixLink[u];
              end;
          end;
      end// u <> Startknoten, d.h. Kante existiert
      last := v;
  end// for i = 1 to m
  result.Terminal[last] := True;
  while last >= 0 do
  begin
      last := result.SuffixLink[last];
      result.Terminal[last] := True;
  end;
end;


Folgendermaßen messe ich die Zeiten:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
//unter Delphi:
  QueryPerformanceFrequency(freq);
  QueryPerformanceCounter(s);
  BuildDawg('das ist ein test');
  //sleep(1000);
  QueryPerformanceCounter(e);
  Showmessage(Format('%.8f', [(e-s)/freq]));



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
// unter Lazarus
  EpikTimer1.Clear;
  EpikTimer1.Start;
  s := EpikTimer1.Elapsed;
  BuildDawg('das ist ein test');
  //sleep(1000);
  e := EpikTimer1.Elapsed;
  Showmessage(Format('%.8f', [e-s]));

Zeit unter Delphi: ca. 0.00009 Sekunden. Unter Lazarus: ca. 0.25 Sekunden. Die Einheiten stimmen - wenn das sleep(1000) drin ist, gibts beides mal ca. eine Sekunde mehr. ;-)

Jetzt die Aufgabe an die Optimierungs-Freak: Woran kann das liegen? Ein Faktor 2-3 mag ich ja noch verschmerzen können, aber das hier ist dann doch etwas zu extrem...


Horst_H - Mo 14.04.08 13:28

Hallo,


Test mit freepascal

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
program testDawG;

uses
  windows,sysutils;
type
  tCharInt = Array[Char] of Integer;
  // Datenstruktur
  TDawgRecord = record
      Init: Integer;  // Der Startknoten
      Len: Array of Integer;        // Speichert die "Länge" der Knoten
      Terminal: Array of Boolean;   // ... die Eigenschaft Terminal  der Knoten
      SuffixLink: Array of Integer; // ... die Suffixlinks der Knoten
      Target:  Array of tCharInt;  // Kanten, Größe: |Nodes| * 256
  end;
var
  e,s,freq: Int64;

function BuildDawg(const p: AnsiString): TDawgRecord;
var
  e,s: Int64;
  m,i,NodeCount: Integer;
  last, u, v, w, r: Integer;
  c:Char;
  CopyC: Char;
  pCharInt : ^tCharInt;
begin
  QueryPerformanceCounter(s);
  m := length(p);
  with result do
    begin
    Setlength(Len, 2*m+2);
    Setlength(Terminal, 2*m+2);
    Setlength(SuffixLink, 2*m+2);
    Setlength(Target, 2*m+2);
    Init := 0;
    SuffixLink[0] := -1;
    end;
with result do // Ob mit oder ohne with, es tut sich nichts..
  begin
//Mit Zeiger brachte auch nichts
  pCharINt := @Target[0];
  for c := Low(Char) to High(Char) do
     pCharInt^[c]:= -1;

  NodeCount := 0;
  last := 0;
  for i := 1 to m do
    begin
      u := last;
      inc(NodeCount); // neuen Knoten einfügen
      v := NodeCount;
      Len[v] := Len[u] + 1;
      // Knoten initialisieren
      Terminal[v] := False;
      for c := Low(Char) to High(Char) do
        Target[v][c] := -1;

      While (u > 0and (Target[u,p[i]] = -1do
        begin
        // Kante einfügen
        Target[u,p[i]] := v;
        u := SuffixLink[u];
        end;


      if Target[u,p[i]] = -1  then
        begin
        // Wir sind am Startknoten
        Target[0,p[i]] := v;
        SuffixLink[v] := 0;
        end
      else
        begin
        // Kante existiert
        w := Target[u,p[i]];
        if Len[u] + 1 = Len[w] then
          begin
          // (u,w) ist feste Kante
          SuffixLink[v] := w;
          end
        else
          begin

          inc(NodeCount);
          r := NodeCount;
          Terminal[r] := False;
          //  Knoten w kopieren
          for CopyC := Low(Char) to High(Char) do
            Target[r,CopyC] := Target[w,CopyC];
          SuffixLink[r] := SuffixLink[w];
          Len[r] := len[u] + 1;
          SuffixLink[w] := r;
          SuffixLink[v] := r;
          while (u >= 0and (Target[u,p[i]] = w) do
            begin
            Target[u,p[i]] := r;
            u := SuffixLink[u];
            end;

          end;
      end// u <> Startknoten, d.h. Kante existiert

      last := v;
  end// for i = 1 to m

  Terminal[last] := True;
  while last >= 0 do
    begin
    last := SuffixLink[last];
    Terminal[last] := True;
    end;
  writeln(nodecount);
  QueryPerformanceCounter(e);
  writeln(Format('Zeit innerhalb Funktion  %.8f', [(e-s)/freq]));
end;

end;

Begin
  QueryPerformanceFrequency(freq);
  QueryPerformanceCounter(s);
  BuildDawg('das ist ein test');
  //sleep(1000);
  QueryPerformanceCounter(e);
  writeln(Format('%.8f', [(e-s)/freq]));
end.


Ergibt
22
Zeit innerhalb Funktion 0,00019109
0,22291409



Na da kann man nur EXTREM staunen...
Da hilft es, wenn man statt function eine procedure mit out Parameter nimmt funktioniert es in 0.0003 Sekunden

Delphi-Quelltext
1:
procedure BuildDawg(const p: AnsiString,result:TDawgRecord);//result als Gag damit ich nichts umschreiben muss                    


Gruß Horst


Gausi - Mo 14.04.08 13:52

Hey, danke! :D

Hab die Funktion grade mal zu einer Prozedur umgebaut - das geht ja wirklich. Komischerweise dauert der Aufbau weiterhin solange, wenn ich die Funktion außerhalb eines Button-Clicks in einer anderen Unit benutze. :gruebel:

Hat jemand ne Idee, warum das als Funktion solange dauert, und als Prozedur mit var/out-Parameter so schnell ist?


BenBE - Mi 16.04.08 15:27

Bei VAR-\OUT-Parametern stellt die Aufrufende Funktion den Ergebnis-Puffer bereit, bei Funktionen wird dieser i.d.R. von der aufgerufenen Funktion alloziiert.

Wo liegt da der Unterschied: Wenn man Rekursive Funktionen hat, wird u.U. der globale Aufruf-Puffer einfach durchgeschleift und es wird nicht jedes Mal eine Kopie angelegt. Das spart Zeit UND der Compiler kann Optimierungen vornehmen, die so tun, als ob das Funktionsergebnis durch Manipulation innerhalb der Routine entstehen; brauch also nicht vorher irgendwas sichern, was er so einfach nur umverteilen kann, weil er weiß, wie die Verarbeitung abläuft ...