const bei Variablen als Funktionsparameter
-
Man müsste Mal benchmarken, ob bei einer for-Schleife mit Bedingung auf die Containergröße dieselbe wegoptimiert wird, wenn sie sich nicht ändert.
Ich habe jedenfalls in meiner Rechen-Engine eine Schleife, die schnell sein muss und ungefähr 1 Mio. Mal pro Sekunde aufgerufen wird. Und ob ich jetzt das Attribut der Vektorgröße abfrage oder nicht, fällt da überhaupt nicht ins Gewicht. Vermutlich wird die Größe intern doch sowieso in irgendein Register geschoben.
Wenn Deine Funktion aber noch performancekritischer und dennoch nicht so trivial ist, dass man die Schleife durch eine nicht-iterative Variante ersetzen kann, würde ich es einfach Mal disassemblieren und schauen, was passiert. Oder eben wie gesagt benchmarken.
-
Swordfish schrieb:
tommy_tom_tom schrieb:
Ist die Reference auf int performanter als eine Kopie? Wäre nicht ein Kopie besser bei kleinen Datentypen?
Richtig. Nimm Bei Typen bis nicht viel größer als ein Pointer keine Zeiger/Referenzen.
Wenn ich innerhalb eines Scopes ein Objekt erzeuge und im selben Scope eine Referenz auf dieses Objekt definiere... da hoffe ich doch, dass es dann wirklich nur ein Aliasname gibt, und da nicht intern irgendwie ein Zeiger erstellt wird und der dann jedesmal dereferenziert wird. Ich gehe davon aus, dass es bei einer Paramterübergabe eventuell so ist, aber nicht wenn ich mich im selben Scope befinde. Oder?
-
...
-
Swordfish schrieb:
Sone schrieb:
[...] und bekam als Antwort, dass man so wenig wie möglich über die Implementierung der Funktion nach außen geben sollte - sei es durch die Typen von Funktionsargumenten o. ä.
Welche Information hast du bei
int foo( int const bar );
mehr über die Implementierung als bei
int foo( int bar );
Außer, dass du weißt, das ich den Parameter
bar
in der Funktion nur lesen werde? Was ist daran phöse?Keine Ahnung. Ich stimme dir zu. Nur lehrt die Erfahrung, dass ich lieber das Denken sein lassen sollte, und auf die Pros hören, und da Shade of Mine das gesagt hat... tjo...
Swordfish schrieb:
Sone schrieb:
Also, Parameter
const
machen schön lassen. Es bringt keinen Vorteil, gerade nicht bei solch einem Einzeiler.Es hat zumindest einen Vorteil: Sollte ich aus versehen einen Parameter innerhalb der Funktion ändern wollen, macht mich durch
const
der Compiler darauf aufmerksam.Ja.. na wie gesagt, s. o.
-
krümelkacker schrieb:
Ich hoffe, du weißt, dass man
header
int foo(int a, int b);
cpp
int foo(const int a, const int b) { return a+b; }
schreiben kann. Die Signaturen sind äquivalent, da das
const
auf dem obersten Level (und nur dort) quasi ignoriert wird. Der einzige Unterschied ist hier bei der const-Version innerhalb der Implementierung, dassa
undb
konstante Lvalues sind. Dem Aufrufer ist das völlig egal, weila
undb
funktionslokale Variablen sind, an die er eh nicht ohne weiteres rankommt.Nein, das wusste ich in der Tat nicht.
Das ist ja einfach geil.
-
krümelkacker schrieb:
Ich hoffe, du weißt, dass man
header
int foo(int a, int b);
cpp
int foo(const int a, const int b) { return a+b; }
schreiben kann. Die Signaturen sind äquivalent, da das
const
auf dem obersten Level (und nur dort) quasi ignoriert wird. Der einzige Unterschied ist hier bei der const-Version innerhalb der Implementierung, dassa
undb
konstante Lvalues sind. Dem Aufrufer ist das völlig egal, weila
undb
funktionslokale Variablen sind, an die er eh nicht ohne weiteres rankommt.Nebenbei bemerkt gilt dise Transformation ebenso (wie array->pointer und function->pointer) nur für Funktionsparameter.
const void foo(); void foo();
sind unterschiedliche Funktionen (und können nat. auch nicht so überladen werden).
krümelkacker schrieb:
@OP: Ich denke nicht, dass das const hier mehr Optimierung erlaubt. Es ist nur eine Möglichkeit, dem Compiler zu sagen, dass wenn man versehentlich eine solche Variable zu ändern versucht, dass das dann einen Compile-Fehler provoziert.
Man kann ggf. etwas konstruieren wie
void opaque(const int&); void for_each(const int first, const int last) { for ( ; first != last; ++first ) opaque(last); }
Das sind aber schon fast pathologische Fälle.
-
tommy_tom_tom schrieb:
void multi(int var__) { return var__*var__; }
Bezeichner mit doppelten Unterstrichen sind reserviert. Das ist also undefiniertes Verhalten (wenn der Rückgabetyp stimmen würde).
Zum Thema: Dem Compiler ist es egal ob bei einem Parameter
const
steht oder nicht. Ob die Variable verändert wird, kann er beim Optimieren selbst feststellen.
Für den menschlichen Leser wäre es aber interessant schnell zu erkennen, ob die Variable in der Funktion jemals verändert wird. Da die wenigsten Argumente in der Praxis verändert werden, wäre ein implizitesconst
sinnvoll (mutable
für nicht-const
). Damit müsste man die Parameterliste der Deklaration nicht jedes Mal für die Definition anpassen. Da das Anpassen nervig und zeitaufwendig ist, macht das fast niemand.
Aus historischen Gründen kann man natürlich nicht die Sprache so verändern.
Ich könnte mir aber vorstellen, dieses Verhalten für Teile der Übersetzungseinheit zu aktivieren.#if __cplusplus >= 204202L # define MUTABLE mutable #else # define MUTABLE #endif #pragma push_default_const_arguments(1) int inc(int a) { ++a; //ill-formed, weil a implizit ein const int ist return a; } int dec(int MUTABLE a) { --a; return a; } int sq(int a) //bei der typischen, korrekten Funktion ändert sich nichts { return a * a; } #pragma pop_default_const_arguments
-
Swordfish schrieb:
Außer, dass du weißt, das ich den Parameter
bar
in der Funktion nur lesen werde? Was ist daran phöse?Nope, diese Information hast du nicht. Ich kann den Wert in der Funktion wenn ich will auch beschreiben...
Es hat zumindest einen Vorteil: Sollte ich aus versehen einen Parameter innerhalb der Funktion ändern wollen, macht mich durch
const
der Compiler darauf aufmerksam.Es handelt sich bei
void foo(int const i);
nur um die Deklaration der Funktion. Ob hier ein const steht oder nicht ist dem Compiler egal.In der Implementierung kann man das const stehen lassen oder wegnehmen, der Compiler kümmert sich nicht darum.
Deshalb: das const in der Deklaration der Funktion ist schlecht: es ist unnötig und sagt nichts aus - was bei const immer böse ist, denn const soll ja auf etwas hinweisen. In diesem Fall aber ist das const zu ignorieren.
In der Definition der Funktion kann man nun theoretisch das const setzen wenn man gerne unnötig Leute verwirrt. Das Problem dabei ist, dass man manchmal den Parameter aber doch ändern will, weil es für den Algorithmus halt gerade passt. Nun muss man die Signatur aber plötzlich anpassen. Und dann hat man komische Einträge in der Source Verwaltung in Zeilen die sich eigentlich nie ändern sollten...
-
camper schrieb:
const void foo(); void foo();
sind unterschiedliche Funktionen (und können nat. auch nicht so überladen werden)
100 Pluspunkte für "const void" als Rückgabetyp!
Also, die Adressen solcher Funktionen kann ich jedenfalls mit dem GCC 4.7.2 nicht vergleichen, weil die Typen wie erwartet nicht übereinstimmen.Was mich aber gerade wundert ist, dass mir decltype des GCCs (4.7.2) sagt, dass das erste foo() void und nicht const als Return-Typ hat. Das ist glaub'ich ein Fehlverhalten des Compilers. Ich hätte folgendes erwartet:
decltype(foo()) --> const void // sollte der Rückgabetyp der Funktion sein decltype((foo())) --> void // sollte den Typ und die L/Rvalueness des Ausdrucks beschreiben
(unter der Annahme, dass void sich auch wie ein skalarer Typ bzgl const verhält)
Ich bekomme aber beide Male nur "void" zurück.
Was sagt der Experte?
-
...
-
krümelkacker schrieb:
Was mich aber gerade wundert ist, dass mir decltype des GCCs (4.7.2) sagt, dass das erste foo() void und nicht const als Return-Typ hat. Das ist glaub'ich ein Fehlverhalten des Compilers. Ich hätte folgendes erwartet:
decltype(foo()) --> const void // sollte der Rückgabetyp der Funktion sein decltype((foo())) --> void // sollte den Typ und die L/Rvalueness des Ausdrucks beschreiben
(unter der Annahme, dass void sich auch wie ein skalarer Typ bzgl const verhält)
Ich bekomme aber beide Male nur "void" zurück.
Klammerung spielt bei decltype nur eine Rolle, wenn es der Ausdruck ohne Klammer ein id-Ausdruck oder ein Klassenmemberzugriff ist (hier nicht der Fall).
n3337 schrieb:
7.1.6.2/4 The type denoted by decltype(e) is defined as follows:
— if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e)
is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions,
the program is ill-formed;
— otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;
— otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
— otherwise, decltype(e) is the type of e.foo() ist weder ein id-Ausdruck noch ein Klassenmemberzugriff. Ausserdem ist es ein prvalue, also ist die letzte Alternative anzuwenden.
Und der Typ des Funktionsaufrufes ist void, da nicht-Klassen-prvalues stets unqualifiziert sind (3.10/4), gerade deswegen ist ja cv-Qualifizierung von Rückgabewerten in diesen Fällen so sinnlos.
-
N'abend,
ich hab mal nen banalen Test gemacht. Ein paar Schleifen mit den anfangs erwähnten Punkten... In der main() werden die Funktionen von einem for-loop gerufen und laufen ihren eigenen loop durch.
Die Zeitangaben sind in Sekunden und sind nicht mehr als die Ausführungszeit des Programmes.Ist mit gcc ohne jegliche Optimierung kompiliert.
Das Ergebnis erstaunt mich etwas... die Referenzen waren am schnellsten. Ob als Funktionsargument oder als Referenz im Rumpf.
// Loops void loop(int condition__) { for(int i{0} ; i!=condition__ ; ++i); } void loop_const(int const condition__) { for(int i{0} ; i!=condition__ ; ++i); } void loop_const_copy(int condition__) { int const condition_copy{condition__}; for(int i{0} ; i!=condition_copy ; ++i); } void loop_const_ref(int condition__) { int const& condition_ref{condition__}; for(int i{0} ; i!=condition_ref ; ++i); } void loop_const_byref(int const& condition_ref__) { for(int i{0} ; i!=condition_ref__ ; ++i); } // Generic 1 template <int I> struct Int2Type{ enum{value = I};}; void gen_loop(Int2Type<0>){/* loop end */} template<int I> void gen_loop(Int2Type<I>) { gen_loop(Int2Type<I-1>()); } // Generic 2 template<int i>struct genLOOP{ static inline void EXEC() { genLOOP<i-1>::EXEC(); }}; template<>struct genLOOP<0>{static inline void EXEC(){/* loop end */}}; int main() { /* static constexpr int first_test{100000000}; for(int i{0}; i!=first_test ; ++i) //loop(100); // : ~23.6 //loop_const(100); // : ~23.2 //loop_const_copy(100); // : ~23.7 //loop_const_ref(100); // : ~19.9 //loop_const_byref(100); // : ~19.7 //gen_loop(Int2Type<100>()); // : ~88.5 genLOOP<100>::EXEC(); // : ~75.3 */ static constexpr int second_test{1000}; for(int i{0}; i!=second_test ; ++i) //loop(100000000); // : ~225 //loop_const(100000000); // : ~223 //loop_const_copy(100000000); // : ~223 //loop_const_ref(100000000); // : ~187.6 loop_const_byref(100000000); // : ~187.0 //gen_loop(Int2Type<10000>()); // MUCH TO COMPILE ;-) //genLOOP<10000>::EXEC(); // MUCH TO COMPILE ;-) return 0; }
-
TyRoXx schrieb:
tommy_tom_tom schrieb:
void multi(int var__) { return var__*var__; }
Bezeichner mit doppelten Unterstrichen sind reserviert. Das ist also undefiniertes Verhalten (wenn der Rückgabetyp stimmen würde).
Wusst ich nicht... wer sagt einem denn so-was, also erst jetzt?
-
tommy_tom_tom schrieb:
Ist mit gcc ohne jegliche Optimierung kompiliert.
-
camper schrieb:
tommy_tom_tom schrieb:
Ist mit gcc ohne jegliche Optimierung kompiliert.
kein -O ... oder weitere manuell zu aktivierenden Optionen.
-
tommy_tom_tom schrieb:
camper schrieb:
tommy_tom_tom schrieb:
Ist mit gcc ohne jegliche Optimierung kompiliert.
kein -O ... oder weitere manuell zu aktivierenden Optionen.
Oder willst auf g++ hinaus?
-
camper schrieb:
Klammerung spielt bei decltype nur eine Rolle, wenn es der Ausdruck ohne Klammer ein id-Ausdruck oder ein Klassenmemberzugriff ist (hier nicht der Fall).
Ok, dann hatte ich das falsch in Erinnerung. Ich dachte, man kommt noch an den deklarierten Rückgabetypen einer Funktion dran.
-
tommy_tom_tom schrieb:
camper schrieb:
tommy_tom_tom schrieb:
Ist mit gcc ohne jegliche Optimierung kompiliert.
kein -O ... oder weitere manuell zu aktivierenden Optionen.
camper wollte sagen, Performance-Vergleiche ohne Optimierung, also kein Release-Build, machen keinen Sinn. Du gibst deinem Kunden ja auch keine Debug-Version oder ;).
-
out schrieb:
tommy_tom_tom schrieb:
camper schrieb:
tommy_tom_tom schrieb:
Ist mit gcc ohne jegliche Optimierung kompiliert.
kein -O ... oder weitere manuell zu aktivierenden Optionen.
camper wollte sagen, Performance-Vergleiche ohne Optimierung, also kein Release-Build, machen keinen Sinn. Du gibst deinem Kunden ja auch keine Debug-Version oder ;).
Klar, geb ich -> Bananen sind im Trend.
-
out schrieb:
tommy_tom_tom schrieb:
camper schrieb:
tommy_tom_tom schrieb:
Ist mit gcc ohne jegliche Optimierung kompiliert.
kein -O ... oder weitere manuell zu aktivierenden Optionen.
camper wollte sagen, Performance-Vergleiche ohne Optimierung, also kein Release-Build, machen keinen Sinn. Du gibst deinem Kunden ja auch keine Debug-Version oder ;).
Ok, um der berechtigten, leisen Kritik von camper nachzukommen.
Mit -O2 ist's rille wie man's formuliert.
Zumindest kann ich keinen Unterschied feststellen (ich hatte auch grössere Werte eingesetzt - hab den Test nur für die Generics reduziert - welche in dem Fall langsamer sind)Man müsste wohl das Szenario anders gestalten oder es ist eben Rille
...
#include <vector> typedef int TYPE_test; static constexpr TYPE_test test_size{50000}; static std::vector<int> test_vector{}; void loop(int condition__) { for(TYPE_test i{0} ; i!=condition__ ; ++i) test_vector[i] = 0; } void loop_const(TYPE_test const condition__) { for(TYPE_test i{0} ; i!=condition__ ; ++i) test_vector[i] = 0; } void loop_const_copy(TYPE_test condition__) { TYPE_test const condition_copy{condition__}; for(TYPE_test i{0} ; i!=condition_copy ; ++i) test_vector[i] = 0; } void loop_const_ref(TYPE_test condition__) { TYPE_test const& condition_ref{condition__}; for(TYPE_test i{0} ; i!=condition_ref ; ++i) test_vector[i] = 0; } void loop_const_byref(TYPE_test const& condition_ref__) { for(TYPE_test i{0} ; i!=condition_ref__ ; ++i) test_vector[i] = 0; } // Generic 1 template <TYPE_test i> struct Int2Type{ enum{value = i};}; void gen_loop(Int2Type<0>) {} template<TYPE_test i> void gen_loop(Int2Type<i>) { test_vector[i] = 0; gen_loop(Int2Type<i-1>()); } // Generic 2 template<TYPE_test i>struct genLOOP{ static inline void EXEC() { test_vector[i] = 0; genLOOP<i-1>::EXEC(); }}; template<>struct genLOOP<0>{static inline void EXEC(){/* loop end */}}; int main() { test_vector.resize(test_size); for(TYPE_test i{0}; i!=test_size ; ++i) //loop(test_size); // : 0.85 //loop_const(test_size); // : 0.85 //loop_const_copy(test_size); // : 0.85 //loop_const_ref(test_size); // : 0.85 //loop_const_byref(test_size); // : 0.85 //gen_loop(Int2Type<test_size>()); // 1.77 //genLOOP<test_size>::EXEC(); // 1.87 return 0; }
Wer Zeit hat kann gern die Werte erhöhen und das ne halbe Stunde laufen lassen...