Autor |
Beitrag |
Mathematiker
      
Beiträge: 2622
Erhaltene Danke: 1447
Win 7, 8.1, 10
Delphi 5, 7, 10.1
|
Verfasst: So 10.05.15 10:32
Hallo,
bei meinem Versuch ein Programm zu "optimieren" bin ich wieder auf ein Problem gestoßen, dass ich nicht verstehe. Der Sonntag ist auf Anweisung meiner Frau  zwar computerfrei, aber es lässt mir keine Ruhe und sie ist gerade beschäftigt.
Konkret: Bisher habe ich einige globale Variablen benutzt (ich weiß, die sind "böse") und will sie in den private-Abschnitt der jeweiligen Klasse verschieben. Funktioniert problemlos, warum auch nicht.
Genau verstanden, warum dies programmtechnisch besser ist, habe ich noch nicht. Das ist aber auch nicht mein Problem.
Das Problem ist: Verschiebe ich z.B. nur eine Variable
Delphi-Quelltext 1: 2:
| implementation var feld : integer; | nach
Delphi-Quelltext 1: 2:
| private feld : integer; | steigt die Größe der Unit (Debuginformationen abgeschaltet) um 80 Byte und die Exe selbst wird ein halbes KByte größer.
Ähnliches geschieht auch bei anderen globalen Variablen. Mitunter reduziert sich die Größe aber erheblich, mitunter steigt sie extrem an.
Insbesondere bei Feldern wie z.B.
Delphi-Quelltext 1:
| belegung : array[0..15,0..15] of integer; | wird in einer konkreten Situation die Unit 200 Byte kleiner, die Exe aber 1 KB größer.
Dies sind alles keine dramatischen Veränderungen; allerdings in der Summe kann einiges zusammenkommen.
Ich verstehe einfach das Prinzip bzw. die dahinter steckende Logik nicht.
Ganz verrückt ist, dass ich manchmal, aber selten, beim Verschieben in den private-Teil auch den ominösen internen Fehler BC2241 bekomme.
Weiß jemand von Euch, was hier vorgeht?
Danke
Mathematiker
Nachtrag: Es geht noch heftiger. Bei der Struktur
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| type teinzel = record ecke : array[1..8] of record x,y,z:double end; mx,my,mz : double; szahl : integer; seite : array[1..6] of integer; mi : array[1..6] of record x,y,z:double end; end; twuerfel = array[1..4,1..4,1..4] of teinzel; | und 3 Variablen vom Typ twuerfel sind es im private-Teil gleich 10KB mehr.
_________________ Töten im Krieg ist nach meiner Auffassung um nichts besser als gewöhnlicher Mord. Albert Einstein
|
|
Delphi-Laie
      
Beiträge: 1600
Erhaltene Danke: 232
Delphi 2 - RAD-Studio 10.1 Berlin
|
Verfasst: So 10.05.15 12:12
Zuletzt bearbeitet von Delphi-Laie am So 10.05.15 14:58, insgesamt 5-mal bearbeitet
Für diesen Beitrag haben gedankt: Mathematiker
|
|
jaenicke
      
Beiträge: 19312
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: So 10.05.15 14:18
Wenn eine Variable nur im privaten Teil einer Klasse vorkommt, kann man auch nur innerhalb der Klasse darauf zugreifen. Auch bei Veröffentlichung nach außen durch eine Property kommt man nur über die Property heran. Man weiß also, dass nicht irgendwo im Code noch ein separater Zugriff existiert, der doch noch etwas anderes hineinschreibt und kann über Getter und Setter jeden Zugriff von außen kontrollieren (notfalls im Debugger).
Die Größe der Exe ändert sich unter anderem durch den Code, der zum Zugriff erzeugt wird. Wenn man z.B. von außen auf eine Property mit Index zugreift, muss die Berechnung der Position in einem Array nur einmal im Getter und Setter im Code stehen, während von außen nur noch Funktionsaufrufe passieren. Das ist nur ein Beispiel, konkret kann man das nur im Assemblercode anschauen was im konkreten Fall passiert.
Für diesen Beitrag haben gedankt: Mathematiker
|
|
Popov
      
Beiträge: 1655
Erhaltene Danke: 13
WinXP Prof.
Bei Kleinigkeiten D3Pro, bei größeren Sachen D6Pro oder D7
|
Verfasst: So 10.05.15 14:46
Bevor wir weiter darüber reden, zuerst einer Klärung: reden wir über (Textdatei) Pas-Unit oder (Binärdatei) Dcu-Unit?
Zuerst allgemein zu Datenträgern. Sie bestehen aus verschiedenen Bereichen, eines davon ist der Sektor bzw. Datenblock. Ein Datenblock ist die kleinste Einheit auf dem Datenträger, noch kleiner geht es nicht. Wenn also Informationen auf den Datenträger gespeichert werden, dann wird mindestens ein Datenblock verbraucht, auch wenn die eigentliche Information kleiner ist. Selbst wenn es nur 1 Byte ist, es wird in dem Fall 1 Datenblock verbracht.
Das kann man auch selber testen. Auf der Festplatte über den Datenexplorer eine leere Txt-Textdatei erstellen. Die Dateieigenschaften sagen uns, dass die Datei 0 Byte groß ist. Die Datei ist ja noch leer. Nun schreiben wir ein Ascii-Zeichen rein, z. B. ein A, das macht 1 Byte. Die Größe der Datei ist jetzt 1 Byte, auf dem Datenträger ist der Verbrauch aber 4 KiB.
Wie gesagt, du warst etwas ungenau mit der Beschreibung der Unit. Welche Unit, wo steigt der Verbrauch? Evtl. meinst du was anderes, aber das auf die Schnelle als Grundinformation.
Was die Exe angeht, nun, der Delphi-Compiler ist schon gut, d. h. er optimiert und wirft vieles raus was der Nutzer für wichtig hält, aber letztendlich unwichtig ist. Trotzdem, man sollte stets in Hinterkopf behalten, dass man hier nicht das Programm selbst in Assembler programmiert (damit meine ich nicht kleine Codestückchen, sondern ganzes Programm), wo drei weitere Bytes nur drei weitere Bytes bedeuten, sondern es einem Compiler überlässt den Code zu deuten. Und wenn der Compiler meint, dass man für die Verarbeitung der drei Bytes eine Routine aus der Bibliothek braucht die 10 KiB groß ist, dann erhöht sich alles eben um 10 KiB.
Zitat: | Genau verstanden, warum dies programmtechnisch besser ist, habe ich noch nicht. |
Das ist wieder so ein Thema für eine Grundsatzdiskussion
Delphi-Laie hat es schon angedeutet, man kann damit gelegentlich so kräftig auf die Schnauze fallen. Beispiel:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| var i: Integer;
procedure TForm1.Button1Click(Sender: TObject); begin for i := 0 to 5 do ShowMessage('Schleife ' + IntToStr(i)); end;
procedure TForm1.Button2Click(Sender: TObject); begin ShowMessage('i wurde in der anderen Prozedur gezählt bis: ' + IntToStr(i - 1)); end; |
Obwohl in der einen Prozedur i für eine Schleife genutzt wird, kann man in der zweiten Prozedur den Wert von i anrufen.
Kann praktisch sein, aber:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| var i: Integer;
procedure TForm1.Button1Click(Sender: TObject); begin for i := 0 to 5 do ShowMessage('Schleife ' + IntToStr(i)); end;
procedure TForm1.Button2Click(Sender: TObject); var i: Integer; begin ShowMessage('i wurde in der anderen Prozedur gezählt bis: ' + IntToStr(i - 1)); end; |
Hier stimmt das Ergebnis plötzlich nicht mehr. Warum? Weil hier i in der zweiten Prozedur eine lokale Variable ist. Delphi gibt der lokalen Variable bei gleichem Namen immer den Vorzug.
Bei so einem Miniprogramm ist das egal, man sieht ob i hier global oder lokal ist. Was ist aber mit einem großen Projekt der über mehrere Units geht und wo man eine globale Variable Width für die Breite eines Werkstücks nutzt? Und dann kommt ein Kollege und braucht Width für die Breite eines Smilies. Eigentlich wollte er Width lokal nutzen, hat aber vergessen es lokal einzutragen. Natürlich gab es keine Fehlermeldung, denn der Compiler fand eine Width Variable (die globale). Und plötzlich wundern sich alle, dass aus irgendeinem Grund das Werkstück länger wird, je breiter das Smili lacht.
_________________ Popov
Für diesen Beitrag haben gedankt: Mathematiker
|
|
Delphi-Laie
      
Beiträge: 1600
Erhaltene Danke: 232
Delphi 2 - RAD-Studio 10.1 Berlin
|
Verfasst: So 10.05.15 14:52
Noch etwas zu den Compilatsgrößen: Die DCUs werden auch umso größer, je mehr der Quelltext auf möglichst viele Zeilen "gestreckt" wird, und umso kleiner, je mehr der Quelltext komprimiert wird, also möglichst viele Anweisungen in jeder Zeile stehen. Das ist insofern plausibel, weil im ersteren Falle mehr Breakpoints gesetzt werden (müssen). Was mich nur wundert: Daß die Größe der Exe-Dateien von diesen DCU-Größen nicht abzuhängen scheint. Ob die Breakpoints beim Linken wieder entfernt werden - und das bei der atemberaubenden Geschwindigkeit des Compilers (inkl. Linkers)?
Zuletzt bearbeitet von Delphi-Laie am So 10.05.15 14:54, insgesamt 1-mal bearbeitet
Für diesen Beitrag haben gedankt: Mathematiker
|
|
WasWeißDennIch
      
Beiträge: 653
Erhaltene Danke: 160
|
Verfasst: So 10.05.15 14:53
Da im Ausgangspost etwas von "in den private-Abschnitt verschoben" steht, kann es hier gar nicht um globale vs. lokale Variablen gehen, sondern wohl eher um globale Variablen vs. private Felder.
Für diesen Beitrag haben gedankt: Mathematiker
|
|
Mathematiker 
      
Beiträge: 2622
Erhaltene Danke: 1447
Win 7, 8.1, 10
Delphi 5, 7, 10.1
|
Verfasst: So 10.05.15 16:03
Hallo,
Danke an alle für die Hinweise und Erklärungen.
Prinzipiell ist mir schon klar, weshalb es vernünftig ist, keine globalen Variablen zu nutzen und, wenn möglich, solche Konstrukte in die Klasse einzubinden. Deshalb umgehe ich globale Variablen ja auch weitestgehend.
Ich war vor allem verwundert, dass die kompilierte DCU (also nicht die PAS-Datei) teilweise so stark anwächst.
Das Argument, das Variablen/Felder(?) im private-Bereich von außen nicht mehr sichtbar und nicht ohne Weiteres verändert sind, ist einleuchtend.
Dennoch möchte ich "ketzerisch" noch einmal fragen:
Liegen die Variablen global im implementation-Abschnitt (nicht nach interface!), so können sie doch von außen gar nicht "gesehen" werden. Enthält die Unit nur genau eine Klasse und ausschließlich deren Methoden, so sind die Variablen doch auch sicher und ungewollte Reaktionen unmöglich.
Ich werde wohl darüber weiter nachdenken müssen.
Danke nochmals
Mathematiker
_________________ Töten im Krieg ist nach meiner Auffassung um nichts besser als gewöhnlicher Mord. Albert Einstein
|
|
Delphi-Laie
      
Beiträge: 1600
Erhaltene Danke: 232
Delphi 2 - RAD-Studio 10.1 Berlin
|
Verfasst: So 10.05.15 16:20
Mathematiker hat folgendes geschrieben : | Dennoch möchte ich "ketzerisch" noch einmal fragen: |
Eine Frage kam genaugenommen nicht, und ketzerisch war daran auch nichts.
Mathematiker hat folgendes geschrieben : | Liegen die Variablen global im implementation-Abschnitt (nicht nach interface!), so können sie doch von außen gar nicht "gesehen" werden. Enthält die Unit nur genau eine Klasse und ausschließlich deren Methoden, so sind die Variablen doch auch sicher und ungewollte Reaktionen unmöglich.
Ich werde wohl darüber weiter nachdenken müssen. |
Darüber eher nicht, denn das dürfte korrekt sein.
Vielleicht meldet sich Sebastian Jänicke hier auch noch mal zu Wort, denn der weiß bezüglich der Delphiprogrammierung eigentlich alles und deshalb auch zu jedem Thema eine richtige Antwort zu geben....auch das meine ich nicht ironisch.
Zuletzt bearbeitet von Delphi-Laie am So 10.05.15 16:36, insgesamt 1-mal bearbeitet
|
|
WasWeißDennIch
      
Beiträge: 653
Erhaltene Danke: 160
|
Verfasst: So 10.05.15 16:27
Unter "älteren" Delphi-Versionen ist diese globale implementation-Variable sogar die einzige Möglichkeit, so etwas wie Klassenvariablen zu simulieren. Ab Delphi 2006 (wenn ich mich recht entsinne) sind allerdings ein paar diesbezügliche Neuerungen hinzugekommen, nämlich Klassenvariablen (class var) und das Wörtchen strict. Klassenvariablen sind (wie der Name schon vermuten lässt) an eine Klasse gebunden und nicht an Instanzen dieser Klasse. Das bedeutet, dass man wenn man so will "klassenglobale" Variablen deklarieren kann. Deklariert man diese als strict private, so haben andere Klassen keinen Zugriff darauf, auch dann nicht, wenn sie in derselben Unit stehen.
|
|
Popov
      
Beiträge: 1655
Erhaltene Danke: 13
WinXP Prof.
Bei Kleinigkeiten D3Pro, bei größeren Sachen D6Pro oder D7
|
Verfasst: So 10.05.15 16:41
Na ja, ich weiß auch nicht wie man die eine globale Variable nennt. Eine lokale Variable nenne ich lokale Variable, eine globale Variable (im Interface-Teil) nenne ich globale Variable. Wie man die globale Variable im Implementation-Teil nennt, habe ich nie nachgedacht. Sie ist Unit-Global, auf der anderen Seite auch Programm-Lokal. Letztendlich ist sie aber global.
Dann gibt es noch die gefakte global Variable, die man der Welt als nicht global verkaufen will, sondern als Feldvariable. Statt sie global zu setzten, packt man sie in das private-Teil des Formulars, knallt noch ein F davor und sagt: ätschibätsch, die ist ja nicht global:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| type TForm1 = class(TForm) private FMeineVariable: Integer; public end; |
Technisch gesehen nicht. Praktisch gesehen stellt sich die Frage inwieweit sie ein Teil der Klasse ist, wenn ich sie global (innerhalb der Unit) nutze? Aber klar, genaugenommen ist sie dann nicht mehr global.
Letztendlich ist das Problem klar - es gibt die prozeduale und die objektorientierte Programmierung. Es widerspricht aber der Logik wieso ein Objekt, nachdem es freigegeben worden ist, immer noch Werte liefert (weil es mit einer globalen Variable gearbeitet hat)? Dann gibt es die prozeduale Programmierung. Auch hier sagt die Logik, es ist ist nur eine Prozedur, also eine in sich geschlossene Einheit. Eine Blackbox. Werte werden per Parameter übergeben, die Prozedur macht ihren festdefinierten Job, fertig. Prozeduren müssen jederzeit austauschbar sein, soweit die Parameter und das Ergebnis gleich sind.
Auf der anderen Seite, gelegentlich ist der Aufwand eine Variable lokal zu halten sehr arbeitsintensiv.
_________________ Popov
|
|
jaenicke
      
Beiträge: 19312
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: So 10.05.15 17:23
Mathematiker hat folgendes geschrieben : | Liegen die Variablen global im implementation-Abschnitt (nicht nach interface!), so können sie doch von außen gar nicht "gesehen" werden. Enthält die Unit nur genau eine Klasse und ausschließlich deren Methoden, so sind die Variablen doch auch sicher und ungewollte Reaktionen unmöglich. |
Das ist natürlich richtig. Und in Delphi selbst gibt es auch solche Variablen, z.B. ClipboardFormats, FileFormats usw., in dem Fall in der Unit Graphics.
Dort werden die Variablen über Zwischenfunktionen jedoch auch in anderen Units bestückt, nur auf die Listen selbst wird nicht zugegriffen.
Wenn hingegen die Listen nur in einer Klasse isoliert benutzt würden, wäre es sinnvoller diese dort unter private bzw. als Klassenvariable zu deklarieren um den Scope möglichst gering zu halten. Je kleiner der Scope ist, desto weniger potentielle Auswirkungen hat eine Änderung der Implementierung z.B. der Klassen hinter diesen Variablen. Denn wenn diese nach außen nur über Funktionen erreichbar sind, reicht es z.B. diese zu testen um die Funktionalität nach außen sicherzustellen.
Für diesen Beitrag haben gedankt: Mathematiker
|
|
Mathematiker 
      
Beiträge: 2622
Erhaltene Danke: 1447
Win 7, 8.1, 10
Delphi 5, 7, 10.1
|
Verfasst: So 10.05.15 21:41
Hallo,
nochmals Danke für die Hinweise.
Wenn ich es richtig verstehe, ist es besser, gleichgültig ob die Exe "wächst", die globalen Variablen wenigstens als private zu "verstecken".
Genau das werde ich, soweit möglich, auch machen.
Beste Grüße
Mathematiker
_________________ Töten im Krieg ist nach meiner Auffassung um nichts besser als gewöhnlicher Mord. Albert Einstein
|
|
|