Delphi-PRAXiS
Seite 3 von 4     123 4      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Code Optimisation: Benutzung von const in prozedur-Köpfen (https://www.delphipraxis.net/192983-code-optimisation-benutzung-von-const-prozedur-koepfen.html)

uligerhardt 9. Jun 2017 07:30

AW: Code Optimisation: Benutzung von const in prozedur-Köpfen
 
Zitat:

Zitat von p80286 (Beitrag 1373944)
Ja klar ist das uralt, aber warum zum Teufel gibt es dann kein
Delphi-Quellcode:
refconst Myvalue
sondern nur ein
Delphi-Quellcode:
const Myvalue
?

Es gibt
Delphi-Quellcode:
const [Ref]
(https://stackoverflow.com/a/19097044/1431618). Natürlich inkompatibel mit prior art (http://wiki.freepascal.org/FPC_New_F...meter_modifier).

jaenicke 9. Jun 2017 08:24

AW: Code Optimisation: Benutzung von const in prozedur-Köpfen
 
Zitat:

Zitat von Namenloser (Beitrag 1373822)
Ja, kannst du. Es schadet zumindest nicht.

So pauschal ist das nicht richtig. Wenn der Parameter eine Interface-Referenz ist und als const deklariert ist, kommt es nicht gut, wenn man ein TXyz.Create dort direkt übergibt. Also ich meine ohne Zwischenvariable vom Typ des Interfaces.

Grund:
Durch das Create selbst wird logischerweise der Referenzzähler nicht erhöht, durch das const ist aber auch die Referenzzählung für den Parameter deaktiviert. Also steht der Referenzzähler auf 0. Übergibt nun der Konstruktor das Interface an eine weitere Routine mit Referenzzählung, wird die Referenz um 1 erhöht und wieder auf 0 gesetzt. Danach ist dann die Referenz ungültig.

Solange man sauber immer mit Zwischenvariablen arbeitet, ist aber alles in Ordnung. So einen Fehler findet man aber leider nicht unbedingt so schnell...

Mavarik 9. Jun 2017 10:29

AW: Code Optimisation: Benutzung von const in prozedur-Köpfen
 
Zitat:

Zitat von Sailor (Beitrag 1373904)
Ehrlich gesagt, verstehe ich die Verwirrung nicht: Man googele doch bitte mal CallByValue, CallByReference und CallByName. Das sind uralte Techniken. Das CONST soll doch das bei CallByValue obligatorische Kopieren des Wertes auf den Stack verhindern, um den nicht unnötig vollzumüllen und macht daher nur bei komplexen Datenstrukturen sowie in Delphi bei Strings Sinn. VAR resp. OUT bedeuten dagegen CallByReference, d.h. es wird die Adresse übergeben und damit ist der Wert der Variablen außerhalb der Prozedur veränderbar. CallByName kennt Delphi glücklicherweise nicht. Das gab es in ALGOL60, mit teilweise verblüffenden Ergebnissen.

Nicht so ganz... Const ist auch ein CallbyReference, der verbotene Schreibzugriff wird über Compilermagic geregelt... Oder?

Mavarik

Zacherl 9. Jun 2017 13:02

AW: Code Optimisation: Benutzung von const in prozedur-Köpfen
 
Zitat:

Zitat von Mavarik (Beitrag 1373979)
Nicht so ganz... Const ist auch ein CallbyReference, der verbotene Schreibzugriff wird über Compilermagic geregelt... Oder?

Das kommt drauf an. Bei trivialen Datentypen verhält sich
Delphi-Quellcode:
const
wie CallByValue:
Delphi-Quellcode:
Unit1.pas.45: A(X);
005CDEEF 8B45F8           mov eax,[ebp-$08] // CallByValue
005CDEF2 E8D9FFFFFF      call A // procedure A(X: Integer)
Unit1.pas.46: B(X);
005CDEF7 8D45F8           lea eax,[ebp-$08] // CallByReference
005CDEFA E8D5FFFFFF      call B // procedure B(var X: Integer)
Unit1.pas.47: C(X);
005CDEFF 8B45F8           mov eax,[ebp-$08] // CallByValue
005CDF02 E8D1FFFFFF      call C // procedure C(const X: Integer)
Bei komplexen Datentypen (Objekte, Records, Interfaces, Strings) dahingegen wie CallByReference (dann wird allerdings auch der Parameter, welcher komplett ohne
Delphi-Quellcode:
const
oder
Delphi-Quellcode:
var
deklariert wurde als CallByReference umgesetzt. Der einzige Unterschied zwischen
Delphi-Quellcode:
const
und "nichts" besteht hier wirklich nur in der Schreibschutzprüfung - welche wie du bereits vermutet hast - zur Compiletime umgesetzt wird):
Delphi-Quellcode:
Unit1.pas.52: A(X);
005CDEEF 8D45F4           lea eax,[ebp-$0c] // CallByReference
005CDEF2 E8D9FFFFFF      call A // procedure A(X: TStruct)
Unit1.pas.53: B(X);
005CDEF7 8D45F4           lea eax,[ebp-$0c] // CallByReference
005CDEFA E8D5FFFFFF      call B // procedure B(var X: TStruct)
Unit1.pas.54: C(X);
005CDEFF 8D45F4           lea eax,[ebp-$0c] // CallByReference
005CDF02 E8D1FFFFFF      call C // procedure C(const X: TStruct)
Hierbei ist Delphi sogar so klug, Records mit z.b. nur einem einzigen Integer Element trotzdem als trivialen Datentyp zu behandeln.

Die eigentliche Optimierung sieht man im Falle des Structs hier ganz schön (jeweils komplett leere Funktionen):
Delphi-Quellcode:
// procedure A(X: TStruct)
Unit1.pas.32: begin
005CDED0 55               push ebp
005CDED1 8BEC            mov ebp,esp
005CDED3 81C46CFEFFFF    add esp,$fffffe6c
005CDED9 56               push esi
005CDEDA 57               push edi
005CDEDB 8BF0             mov esi,eax
005CDEDD 8DBD6CFEFFFF    lea edi,[ebp-$00000194]
005CDEE3 B965000000       mov ecx,$00000065
005CDEE8 F3A5             rep movsd
Unit1.pas.34: end;
005CDEEA 5F              pop edi
005CDEEB 5E              pop esi
005CDEEC 8BE5             mov esp,ebp
005CDEEE 5D              pop ebp
005CDEEF C3               ret

// procedure B(var X: TStruct)
Unit1.pas.37: begin
005CDEF0 55               push ebp
005CDEF1 8BEC            mov ebp,esp
005CDEF3 51               push ecx
005CDEF4 8945FC          mov [ebp-$04],eax
Unit1.pas.39: end;
005CDEF7 59               pop ecx
005CDEF8 5D              pop ebp
005CDEF9 C3               ret

// procedure C(const X: TStruct)
Unit1.pas.42: begin
005CDEFC 55               push ebp
005CDEFD 8BEC            mov ebp,esp
005CDEFF 51               push ecx
005CDF00 8945FC          mov [ebp-$04],eax
Unit1.pas.44: end;
005CDF03 59               pop ecx
005CDF04 5D              pop ebp
005CDF05 C3               ret
Und bei Strings sieht die Sache ähnlich aus (ebenfalls jeweils komplett leere Funktionen):
Delphi-Quellcode:
// procedure A(X: String)
Unit1.pas.32: begin
005CDED0 55               push ebp
005CDED1 8BEC            mov ebp,esp
005CDED3 51               push ecx
005CDED4 8945FC          mov [ebp-$04],eax
005CDED7 8B45FC          mov eax,[ebp-$04]
005CDEDA E881BEE3FF      call @UStrAddRef
005CDEDF 33C0             xor eax,eax
005CDEE1 55               push ebp
005CDEE2 6803DF5C00       push $005cdf03
005CDEE7 64FF30           push dword ptr fs:[eax]
005CDEEA 648920           mov fs:[eax],esp
Unit1.pas.34: end;
005CDEED 33C0             xor eax,eax
005CDEEF 5A              pop edx
005CDEF0 59               pop ecx
005CDEF1 59               pop ecx
005CDEF2 648910           mov fs:[eax],edx
005CDEF5 680ADF5C00       push $005cdf0a
005CDEFA 8D45FC          lea eax,[ebp-$04]
005CDEFD E87ABDE3FF      call @UStrClr
005CDF02 C3               ret
005CDF03 E990B3E3FF      jmp @HandleFinally
005CDF08 EBF0             jmp $005cdefa
005CDF0A 59               pop ecx
005CDF0B 5D              pop ebp
005CDF0C C3               ret

// procedure B(var X: String)
Unit1.pas.37: begin
005CDF10 55               push ebp
005CDF11 8BEC            mov ebp,esp
005CDF13 51               push ecx
005CDF14 8945FC          mov [ebp-$04],eax
Unit1.pas.39: end;
005CDF17 59               pop ecx
005CDF18 5D              pop ebp
005CDF19 C3               ret

// procedure C(const X: String)
Unit1.pas.42: begin
005CDF1C 55               push ebp
005CDF1D 8BEC            mov ebp,esp
005CDF1F 51               push ecx
005CDF20 8945FC          mov [ebp-$04],eax
Unit1.pas.44: end;
005CDF23 59               pop ecx
005CDF24 5D              pop ebp
005CDF25 C3               ret

Mavarik 9. Jun 2017 13:24

AW: Code Optimisation: Benutzung von const in prozedur-Köpfen
 
Zitat:

Zitat von Zacherl (Beitrag 1373995)
Das kommt drauf an. Bei trivialen Datentypen verhält sich
Delphi-Quellcode:
const
wie CallByValue:
Delphi-Quellcode:
Unit1.pas.45: A(X);
005CDEEF 8B45F8           mov eax,[ebp-$08] // CallByValue
005CDEF2 E8D9FFFFFF      call A // procedure A(X: Integer)
Unit1.pas.46: B(X);
005CDEF7 8D45F8           lea eax,[ebp-$08] // CallByReference
005CDEFA E8D5FFFFFF      call B // procedure B(var X: Integer)
Unit1.pas.47: C(X);
005CDEFF 8B45F8           mov eax,[ebp-$08] // CallByValue
005CDF02 E8D1FFFFFF      call C // procedure C(const X: Integer)

Gut... Mov und lea hat laut meiner x86 reference die gleiche Anzahl von Taktzyklen... Also egal... aber wenn es CallByValue ist - ist die nicht Beschreibbarkeit Compilermagic...?

Sailor 9. Jun 2017 13:48

AW: Code Optimisation: Benutzung von const in prozedur-Köpfen
 
Zitat:

Nicht so ganz... Const ist auch ein CallbyReference, der verbotene Schreibzugriff wird über Compilermagic geregelt... Oder?
Nein, es ist so implementiert wie CallByReference. Don't mix the concept with ist realization. Der ganze Wirrwarr ließe sich verhindern durch die Festlegung, daß auf CallByValueParameter nur lesend zugegriffen werden kann, vulgo sie dürfen nicht als lokale Variable mißbraucht werden. Das hat sich natürlich keiner getraut, zweng der Abwärtskompatibilität. Dann hätte der Benutzer völlige Klarheit und der Compilerentwickler freie Hand, wie er das durchzieht. Das jetzige CONST in der Parameterliste ist doch eigentlich ein Implementierungshinweis an den Compiler: Du kannst hier einen Zeiger übergeben, den Stack minimieren, mußt aber Schreibzugriffe sperren. Afaik ist das aber schon dem Wirth im Ursprungspascal durchgerutscht.

Zacherl 9. Jun 2017 14:54

AW: Code Optimisation: Benutzung von const in prozedur-Köpfen
 
Zitat:

Zitat von Mavarik (Beitrag 1374005)
Gut... Mov und lea hat laut meiner x86 reference die gleiche Anzahl von Taktzyklen... Also egal... aber wenn es CallByValue ist - ist die nicht Beschreibbarkeit Compilermagic...?

Genau, tatsächlich ist bei trivialen Datentypen das CallByValue sogar meist performanter, weil entsprechende Werte einfach in Registern liegen bleiben können, während der Wert für eine Referenz ja zwingend auf dem Stack oder dem Heap angesiedelt sein muss. Dementsprechend macht Delphi das hier schon ziemlich gut.

Das mit der Beschreibbarkeit ist auch korrekt. Das passiert komplett zur Compiletime - sowohl für CallByValue, als auch für CallByReference.

Namenloser 9. Jun 2017 18:21

AW: Code Optimisation: Benutzung von const in prozedur-Köpfen
 
Zitat:

Zitat von jaenicke (Beitrag 1373964)
Zitat:

Zitat von Namenloser (Beitrag 1373822)
Ja, kannst du. Es schadet zumindest nicht.

So pauschal ist das nicht richtig. Wenn der Parameter eine Interface-Referenz ist und als const deklariert ist, kommt es nicht gut, wenn man ein TXyz.Create dort direkt übergibt. Also ich meine ohne Zwischenvariable vom Typ des Interfaces.

Grund:
Durch das Create selbst wird logischerweise der Referenzzähler nicht erhöht, durch das const ist aber auch die Referenzzählung für den Parameter deaktiviert. Also steht der Referenzzähler auf 0. Übergibt nun der Konstruktor das Interface an eine weitere Routine mit Referenzzählung, wird die Referenz um 1 erhöht und wieder auf 0 gesetzt. Danach ist dann die Referenz ungültig.

Solange man sauber immer mit Zwischenvariablen arbeitet, ist aber alles in Ordnung. So einen Fehler findet man aber leider nicht unbedingt so schnell...

Wenn das stimmt, ist das meiner Meinung nach ein Bug in Delphi. Ein Referenzzähler sollte immer mit 1 initialisiert sein, nicht mit 0.

himitsu 9. Jun 2017 19:03

AW: Code Optimisation: Benutzung von const in prozedur-Köpfen
 
Zitat:

Zitat von Namenloser (Beitrag 1374078)
Wenn das stimmt, ist das meiner Meinung nach ein Bug in Delphi. Ein Referenzzähler sollte immer mit 1 initialisiert sein, nicht mit 0.

In soeinem Fall sollte Delphi besser eine (interne) lokale Variable generieren und Diese an den Parameter übergeben.

Und nein, denn wenn das schon zu Beginn 1 ist und man übergibt das Interface an eine Variable, dann wäre es danach 2.
Ist die Variable dann weg, wäre es aber immernoch 1 und würde niemals freigegeben.

Namenloser 9. Jun 2017 19:38

AW: Code Optimisation: Benutzung von const in prozedur-Köpfen
 
Zitat:

Zitat von himitsu (Beitrag 1374089)
Zitat:

Zitat von Namenloser (Beitrag 1374078)
Wenn das stimmt, ist das meiner Meinung nach ein Bug in Delphi. Ein Referenzzähler sollte immer mit 1 initialisiert sein, nicht mit 0.

In soeinem Fall sollte Delphi besser eine (interne) lokale Variable generieren und Diese an den Parameter übergeben.

Das kommt auf dasselbe raus, was ich meine. Der Referenzzähler sollte immer bei 1 starten, denn irgendjemand muss ja eine Referenz auf das Objekt haben, ansonsten dürfte es gar nicht existieren. Und wenn es keine gibt, dann muss der Compiler eine generieren.

Ich habe in C schon manuelle Referenzzählung implementiert und dort habe ich es immer so gemacht:

Code:
typedef struct RefCounted {
    int refcount;
} RefCounted;

RefCounted* refcounted_new()
{
    RefCounted *obj = malloc(sizeof(RefCounted));
    obj->refcount = 1;
    return obj;
}

void refcounted_destroy(RefCounted *obj)
{
    free(obj);
}

void addref(RefCounted *obj)
{
    ++(obj->refcount);
}

void release(RefCounted *obj)
{
    --(obj->refcount);
    if (obj->refcount==0) {
        refcounted_destroy(obj);
    }
}


int main()
{
    RefCounted *obj = refcounted_new();
    // Hier KEIN addref(obj)

    blablabla_irgendwas_mit_obj_machen(obj);

    release(obj);
}
So sollte es der Compiler auch machen.


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:48 Uhr.
Seite 3 von 4     123 4      

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz