?
Kleiner Nachtrag, der mir gerade noch eingefallen ist:
Ich habe mein und dein Beispiel auf primitive Datentypen reduziert. Aber in C - und vor allem in C++ - kann man eigene sehr komplexe Datentypen erstellen. Ach was, man braucht sie nicht mal erstellen, es reicht schon, wenn man so Container aus der STL verwendet. Jedes Mal, wenn du ein komplexes Objekt erstellt wird, wird ein Konstruktor aufgerufen. Und der macht halt mit new/delete rum.
Ich habe noch gelehrt bekommen, dass man die Anzahl der dynamischen Speicherreservierungen reduzieren soll, aber mein Hauptgebiet ist auch C, da kann man das, ohne die Philosophie hinter der Sprache zu brechen. C++ bringt einen Haufen eigener Typen und Programme mit, die mit Fehlern von C aufräumen sollen, allerdings zu dem Preis einer enorm erhöhten Komplexität (was übrigens auch der Grund war, warum die Leute dann auf den Java-Zug gesprungen sind. Wenn ich mir die Sprachdefinitionen zwischen C++ und Java anschaue, ist Java in der Komplexität aber deutlich weiter). Dynamische Speicherreservierungen sind teuer. Selbst, wenn du keinen Kernel-Call (Speicherreservierung vom Betriebssystem - in der Regel holt sich new/malloc Speicher in einem großen Block und geht dann im Userspace nur interne Listen durch, ob ein angeforderter Chunk noch in einen Hohlraum passt) hast, muss immer noch die interne Liste durchgegangen werden. Und das gleiche bei delete/free . Sowas ist teuer.
Worauf ich hinauswill, ist: wenn du komplexere Typen verwendest und Call-By-Value machst, dann hast du immer den Aufruf des Konstruktors oder Kopierkonstruktors über dir hängen. Der kann dann, um tiefe Kopien deiner Objekte zu erstellen (oder generell ein Objekt) die dynamische Speicherverwaltung anhauen. Und dann wird das teuer.
Und dann wird das noch teurer, weil du dann bei Funktionsrückkehr noch Destruktorenaufrufe hast, die dann eventuell wieder delete/free machen. Bei solchen Szenarien kannst du dir ernsthaft Sorgen um die Performance machen. Auch, wenn du immer so tolle Aussagen wie "get what you pay for" zu hören bekommst.
[Offtopic]Ich habe dein Programm ja kompiliert, und das Binary (auf Linux) ist so 100 KB Groß. Ungestriped (gestriped sind's dann noch 55 KB). Und braucht drei Sekunden zum Kompilieren.
Als Vergleich: wir hacken hier an einer plattformunabhängigen Bibliothek in C. Quellcode so derzeit 15.000 Zeilen, Kommentare abgezogen, und die Header sind da noch nicht mit eingerechnet. Die braucht ebenfalls 3 Sekunden zum Kompileren, ist aber nur 80 KB groß. Ungestriped. Gestriped dann nur noch 68 KB. Aber das sind auch +15K Zeilen Code.
Ich erwähne das, weil ich immer "get what you pay for" von den C++-Leuten höre. Aber genug offtopic.[/Offtopic]
Vermeiden kannst du das übrigens, indem du Referenzen/Adressen (na gut, Adressen/Zeiger sind C, und in C++ soll man sie nicht verwenden) an die Funktionen übergibst, die du const deklarierst. Also, die Parameter jetzt. Damit sagst du, dass du gerne das Originalobjekt hättest, aber versprichst, es nicht zu ändern. Funktioniert natürlich nur dann, wenn du's dann auch nicht änderst. Wenn doch, dann hilft dir allerdings nicht mal der Wechsel zu C.
Also: bei kleinen/primitiven Typen ist das kein Problem. Bei komplexeren Call-By-Value-Situationen schon.
Als Beispiel auch noch mal dieses Programm:
#include <cstdint>
#include <iostream>
#include <string>
static __inline__ unsigned long long rdtsc(void)
{
unsigned hi, lo;
__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}
void*buffer;
void* bla_ref(const std::string&xxx)
{
return const_cast<char*>(xxx.c_str());
}
void* bla_value(const std::string xxx)
{
return const_cast<char*>(xxx.c_str());
}
uint64_t measure1(char const* fName,void* (*f)(const std::string))
{
int const maxLeft=1000000;
int left=maxLeft;
uint64_t minTime=(uint64_t)-1;
while(left!=0)
{
uint64_t time=-rdtsc();
buffer=f("Bla");
time+=rdtsc();
if(time<minTime)
{
minTime=time;
left=maxLeft;
}
--left;
}
std::cout<<"function "<<fName<<" needs "<<minTime<<" ticks\n";
return minTime;
}
uint64_t measure2(char const* fName,void* (*f)(const std::string&))
{
std::string xxx="bla";
int const maxLeft=1000000;
int left=maxLeft;
uint64_t minTime=(uint64_t)-1;
while(left!=0)
{
uint64_t time=-rdtsc();
buffer=f(xxx);
time+=rdtsc();
if(time<minTime)
{
minTime=time;
left=maxLeft;
}
--left;
}
std::cout<<"function "<<fName<<" needs "<<minTime<<" ticks\n";
return minTime;
}
#define MEASURE1(f) measure1(#f,f)
#define MEASURE2(f) measure2(#f,f)
int main(void)
{
MEASURE1(bla_value);
MEASURE2(bla_ref);
return 0;
}
(Das schlechte C++ bitte ich zu entschuldigen, ich bin wie gesagt eher in C bewandet).
Hier braucht bla_value deutlich länger als bla_ref . Bei mir sind's auf -O0:
function bla_value needs 98 ticks
function bla_ref needs 12 ticks
Auf -O3:
function bla_value needs 56 ticks
function bla_ref needs 12 ticks
Und das sind die schnellsten Läufe. Nicht der Durchschnitt.
Hm, war dann doch nicht so klein.