GC vs RAII
-
Kernel-Calls gibts da eh nicht, die Laugzeitumgebung holt sich gelegentlich sagen wie mal 64kB und gibt 32B-weise raus, das ist voll vernachlässigbar.
Mal die Konstruktoren weggelassen.
100.000 mal new in Java ist nicht mehr als 100.000 mal
char* ret=ptrLast; ptrLast+=sizeof(Thingy) return ret;
, während in C++ da jemand nach freiem Speicher suchen muss.
Java: 1 Takt pro new
C++: 30 Takte pro newBein Aufräumen rächt sich es leider, C++ wieder 30 Takte und Java nicht abschätzbar, theoretisch schneller als 60. Nu hat man in C++ für
Point p(2,3);
auf dem Stack halt 0 zum Allokieren und 0 zum Deallokieren. Und man benutzt nur Stackvariablen. Kosten tun nur die komischen Klassen, die innendrin Freispeicher verwenden wie vector oder so. In Java kostet jede Klasse.
Also der GC ist nicht schuld. Java braucht auch RAII, das ist mal klar.
Und dann schauen wie mal, ob generational Umsortieren (um hot-Speicher in der Nähe zu halten und oft um Cache) die Kosten einbringt, die Zusatzdereferenzierungen kosten. Wir können es nicht wissen. Tun die Prozessorbauer einen doppelreferenzierungsbefehl superscalar rein? Oder sorgen sie allgemeiner für so viel schnellen Cache, daß alle hot-Bereiche normalerweise hot bleiben.
Falls an obiger Darstellung irgendwas lächerlich klingt: Es war nicht meine Absicht. Ich hab Java total ernst genommen. Aber nu lese ich das und es ist eine Verspottung. Komisch.
-
hustbaer schrieb:
Wenn du 100.000 Objekte auf dem Heap haben willst, dann musst du sowohl in Java als auch in C++ 100.000 mal "new" machen und 100.000 mal den Ctor aufrufen. Da besteht kein Unterschied. An was du bei Java denkst wären 100.000 NULL Referenzen. Das ist ganz was anderes. Und auch das kannst du in C++ mit RAII haben, z.B. mit std::unique_ptr bzw. std::shared_ptr.
Stimmt. Habe ich vergessen zu schreiben. Lag wahrscheinlich daran, dass ich im Geiste davon ausgegangen bin, dass es bei Java dann sowieso egal ist von der Laufzeit her (warum eigentlich? Siehe unten). In beiden Sprachen machst du dann Konstruktorenaufrufe. Nur in C++ kannst du die mit
malloc
vermeiden. Oder geht das auch in Java?volkard schrieb:
Kernel-Calls gibts da eh nicht, die Laugzeitumgebung holt sich gelegentlich sagen wie mal 64kB und gibt 32B-weise raus, das ist voll vernachlässigbar.
hustbaer schrieb:
Und natürlich bedeutet new auch nicht einen Kernel-Call pro new Aufruf.
Mit solchen Aussagen wäre ich vorsichtig, das hängt, wie du bereits sagtest, von der Laufzeitumgebung ab. Ich erinnere mich, dass OS X bei jedem
malloc
und jedemfree
einen Kernel-Call ausgelöst hat.Kommt halt immer darauf an, welches
malloc
und welchesfree
du verwendest. Ordentliche Implementierungen holen sich direkt größere Chunks vom Kernel und verwalten reservierte Speicherbereiche in einer verketteten Liste. Ist auch langsamer als der Stack, aber immer noch besser als immer Kernel-Calls. Ich versuche immer, die Anzahl der Speicheranforderungen gering zu halten, seit ich mal gelesen habe, dass der Firefox beim einfachen Öffnen einer Seite und dann Schließen ein paar Millionenmalloc
undcalloc
macht.Meine Aussage war ja auch nicht, dass
new
immer Kernel-calls macht.volkard schrieb:
Also der GC ist nicht schuld. Java braucht auch RAII, das ist mal klar.
Gregor schrieb:
Es gibt beim GC auch noch ganz andere Aspekte. Der Standard-GC in Java ist zum Beispiel AFAIK ein generationenbasierter GC. Das sind GCs, die letztendlich zu einer geringeren Fragmentierung des Speichers fuehren. Ich denke, bei einer naiven Nutzung des Heaps in C++ hat man bezueglich Speicherfragmentierung ein groesseres Problem. ...beim Stack fragmentiert natuerlich gar nichts.
Habe nicht geschrieben, dass Java wegen GC langsam ist, sondern dass GC und Java generell langsamer sind.
In C oder C++ kannst du es natürlich ganz wild treiben und die Optimierungen, die die VM von Java durchführt, selbst machen. Hat den Vorteil, dass du lernst, bewusst mit Speicher umzugehen, und ist dann wieder schneller als Java und verbraucht nicht <dreistellige Zahl einfügen> Megabytes beim Start. Kannst du natürlich auch machen. Habe ich mal für einen Webcrawler in C verwendet. Das Ding lief zwei Tage und hatte dann 120 Sekunden reine Prozessorzeit von einem Kern. Weil ich versuche, den Speicher zu erhalten. Am Besten vor der eigentlichen Programmausführung. Und wenn es ganz wild kommt, habe ich hier in meiner Lib immer noch eindefine
füralloca
.
Meine Tests mit Java waren da nicht so zufriedenstellend. Aber gut, ich mache auch Code in C, Java ist da eher Sekundärsprache. Vielleicht habe ich da nur nicht die Vorteile von Java ausgenutzt (sollte die VM das nicht automatisch machen? Egal), und ich tue der Sprache da unrecht. Wie gesagt, ich lasse mich gerne korrigieren.Hm, jetzt, wo ich so darüber nachdenke, fällt mir noch was ein, mit dem man
mallocs
undfrees
sparen kann. Deswegen mag ich das Forum. Gutes Gedankenfutter hier.volkard schrieb:
Und dann schauen wie mal, ob generational Umsortieren (um hot-Speicher in der Nähe zu halten und oft um Cache) die Kosten einbringt, die Zusatzdereferenzierungen kosten. Wir können es nicht wissen. Tun die Prozessorbauer einen doppelreferenzierungsbefehl superscalar rein? Oder sorgen sie allgemeiner für so viel schnellen Cache, daß alle hot-Bereiche normalerweise hot bleiben.
Nein, ist keine Verspottung. Aber wer kann schon mit Superskalarität etwas anfangen, außer Compilerbauer und Nerds?
-
dachschaden schrieb:
Nein, ist keine Verspottung. Aber wer kann schon mit Superskalarität etwas anfangen, außer Compilerbauer und Nerds?
Alle hier.
Wer Fragen hat, der fragt.
Wer anderer Meinung ist, der widerspricht.
Ich verstehe und liebe C++.de als Fachforum.
-
volkard schrieb:
RAII kümmert sich um alle Ressourcen. Auch offen Datenbankverbindungen und eben alles, was sofort wegmuss. Ohne daß man sich im Code drum kümmern muss.
Dafür gibt's in Java seit Version 7 das try-with-resources Statement.
Manchmal isses gemein lästig, wenn Klassen aus der Standardlib keine tiefen Kopien können.
Beziehst du das auf Java? Die clone()-Methode macht doch genau das.
L. G.,
IBV
-
dachschaden schrieb:
hustbaer schrieb:
Und natürlich bedeutet new auch nicht einen Kernel-Call pro new Aufruf.
Mit solchen Aussagen wäre ich vorsichtig, das hängt, wie du bereits sagtest, von der Laufzeitumgebung ab. Ich erinnere mich (*snip*)
Ist jetzt Interpretationssache.
Ich habe natürlich nicht gemeint dass es nicht möglich wäre dass 1x new == 1x Kernel-Call. Ich meinte bloss üblicherweise, und auf allen Plattformen die kein totaler Schrott sind, so ist.
-
IBV schrieb:
volkard schrieb:
RAII kümmert sich um alle Ressourcen. Auch offen Datenbankverbindungen und eben alles, was sofort wegmuss. Ohne daß man sich im Code drum kümmern muss.
Dafür gibt's in Java seit Version 7 das try-with-resources Statement.
Was auch sehr nötig war. Aber kein Ersatz für RAII wie man es in C++ machen kann ist.
Konkret fehlt z.B. die Möglichkeit Member-Variablen als "owned resource" zu deklarieren. Die Idee dabei wäre dass einer Klasse die "owned resource" Member hat vom Compiler automatisch ein close() Funktion implementiert wird. Bzw. wenn man die "close" Funktion selbst implementiert (weil man noch andere Dinge machen darin muss), es eine Möglichkeit gibt darin dann eine "default-close" Funktion aufzurufen. Die dann wieder das macht was die compilergenerierte "close" Funktion gemacht hätte. Ich programmiere zwar kein Java, aber genau diese Funktion wünsche ich mir in C# schon öfter mal.
Und dann wäre da noch die Sache mit den fehlenden Value-Types. Denn RAII heisst für mich genauso dass ich sowas wie shared_ptr implementieren kann, ohne dass der Client-Programmer wissen und dran denken muss wo er überall eine neue Kopie des shared_ptr erzeugen oder eine bestehende entsorgen muss. Und dafür braucht man Value-Types -- mit RAII Support. Auch das fehlt in C# leider *.
IBV schrieb:
Manchmal isses gemein lästig, wenn Klassen aus der Standardlib keine tiefen Kopien können.
Beziehst du das auf Java? Die clone()-Methode macht doch genau das.
Ne ich denke er meint die Collections, die eben Collections und keine Container sind.
* Ich weiss schon dass es Value-Types aka "structs" in C# gibt. Und dass auch "structs" IDisposable implementieren können. Und dass der Compiler für lokale Variablen von solchen Typen sogar selbständig den IDisposable.Dispose Aufruf einfügt. Nur wird das alles dadurch kaputt gemacht dass "structs" in C# immer "blittable" sind, d.h. einen compilergenerierten Copy-Ctor und Assignment-Operator haben der einfach nur Byte-für-Byte die Daten rumkopiert.
-
try-with-resources ist für mich überhaupt kein Ersatz für RAII.
Das gab es im Prinzip schon immer, denn es macht ja nichts anderes als daraus ein try...catch...finally{dispose} zu basteln.
Es verkürzt einfach nur die Schreibweise.
Aber ich muss (müsste) weiterhin bei jeder Klasse die ich verwende nachschauen, ob die gerne was disposen möchte.
In C++ nutze ich einfach die Klasse und wenn die was aufräumen möchte, muss ich davon nichts wissen.
-
dachschaden schrieb:
- Kann auch langsam sein. Sagen wir, du willst erst mal nur 100.000 Objekte auf dem Heap haben, diese aber nicht unbedingt initialisieren. Mit RAII hast du dann wieder 100.000 Calls des Konstruktors, die gemacht werden müssen.
Der Unterschied ist, dass man in Java alles auf dem Heap anlegt und in C++ eher nicht.
Wenn ich 100.000 Objekte in Java benötige, dann muss ich 100.000 mal new aufrufen und dynamischen Speicher anfordern. Das geht in Java sehr schnell.
Wenn ich 100.000 Objekte in C++ benötige, dann lege ich einen std::vector mit 100.000 Elementen an, welcher alle 100.000 Elemente mit einem new-Aufruf alloziiert. Und das geht mit ziemlicher Sicherheit schneller, als die sehr schnellen 100.000 Aufrufe in Java.
Ich gehe mal davon aus, dass beide Sprachen der Optimierer keine Probleme hat, den Konstruktoraufruf geeignet zu optimieren. In C++ habe ich möglicherweise einen Standardkonstruktor, welcher Inline ist. Da "sieht" der Compiler, was er machen muss und hat die Möglichkeit zu optimieren. Bei Java sieht er es zur Laufzeit und auch dort werden ähnliche Optimierungen erfolgen.
In C++ programmiere ich eben anders. Daher ist es nicht sinnvoll, die Allokation von Speicher zu benchmarken, da ich in C++ eben weniger Allokationen machen muss.
So nebenbei ist in C++ der Code zum anlegen von 100000 Elementen auch wesentlich übersichtlicher:
std::vector<Point> points(100000);
List<Point> points = new ArrayList<Point>(); for (int i = 0; i < 100000; ++i) points.add(new Point());
-
dachschaden schrieb:
So nebenbei ist in C++ der Code zum anlegen von 100000 Elementen auch wesentlich übersichtlicher:
std::vector<Point> points(100000);
List<Point> points = new ArrayList<Point>(); for (int i = 0; i < 100000; ++i) points.add(new Point());
Das ist jetzt ein Scherz oder? Soviel Code für so eine simple Sache?
Mir ist auch eigentlich bei Java egal, ob das theoretisch langsamer oder gleich schnell wie C++ ist. Ich sehe Java-Anwendungen und sehe C++-Anwendungen und da gewinnt bei mir IMMER das C++-Programm in Sachen Performance.
Habt ihr euch mal den neuen SceneBuilder für JavaFX angesehene? Der hat letzlich beim Ausprobieren, von einer Hand voll Widgets, meinen dicken i7 mit guter Grafikkarte locker in die Knie gezwungen. Da ist Battlefield4 genügsamer.
-
Wann habt Ihr das letzte mal eine Liste mit 100.000 "Standardpunkten" benoetigt?
Zeigt doch mal den Unterschied, wenn Ihr 100.000 Punkte erstellt, die alle auf einer Funktion liegen. Also zum Beispiel, wenn Ihr etwas plotten moechtet. Besser noch: Baut mal eine Methode, aus der Ihr so eine Liste an Punkten rauskriegt und zeigt diese inklusive dem Methodenaufruf.
-
tntnet! Du unterschlägst aber, das die C++ Variante nicht der Java-Variante entspricht!
In deinem C++ Beispiel kann man nur Objekte genau eines Typs anlegen: Point! Und zwar ausschließlich Points, keine Objekte die von Point erben.Jetzt kann man natürlich sagen, ich brauche nur den einen Typ. Aber dann sage das auch. So ist der Vergleich leider für den Popo! Weil sobald du Point als Basis-Typ haben willst, ist der C++ Vorteil wieder dahin!
-
Artchi schrieb:
Weil sobald du Point als Basis-Typ haben willst, ist der C++ Vorteil wieder dahin!
std::vector<std::shared_ptr<Point>> points(size, std::make_shared<Point>());
Macht in etwa dasselbe wie der Javacode und ist zeilenmäßig trotzdem besser.
-
Na also, wenn dann bitte gleich richtig. Geht doch! Aber das von Tntnet war ne Frechheit!
Um die Codekürze ging es mir nicht vorrangig. Das ist nur nice-to-have. Es geht darum, was es macht und dann müssen beide Beispiel gleiches können.
-
Nathan schrieb:
std::vector<std::shared_ptr<Point>> points(size, std::make_shared<Point>());
Macht in etwa dasselbe wie der Javacode und ist zeilenmäßig trotzdem besser.
Ist immer noch irrelevant. Niemand braucht 100.000 Punkt Objekte, die alle mit dem Standardkonstruktor erstellt wurden und somit alle gleich sind.
Wenn Ihr zu einem realistischeren Szenario geht, dann wird die Codelänge nicht mehr wesentlich von einander abweichen.
-
Gregor! Ja, das stimmt. Ich verstehe das Ziel dieses künstlichen Konstrukts auch nicht. In der Praxis sieht Code anders aus. Selbst für einen Unittest wäre das Beispiel unrealistisch.
-
Gregor schrieb:
Nathan schrieb:
std::vector<std::shared_ptr<Point>> points(size, std::make_shared<Point>());
Macht in etwa dasselbe wie der Javacode und ist zeilenmäßig trotzdem besser.
Ist immer noch irrelevant. Niemand braucht 100.000 Punkt Objekte, die alle mit dem Standardkonstruktor erstellt wurden und somit alle gleich sind.
Insbesondere macht das nicht 100k Point Objekte, sondern einen Vektor aus 100k shared_ptr die alle auf das selbe Point Objekt verweisen...