NytroX hat geschrieben: ↑15.07.2024, 18:47
Das intern über Status/Either/Error zu machen verursacht halt im regulären Code verteilte Kosten
Ja, stimmt. Das ist das größte Problem daran. Allerdings verursachen Exceptions das auch - einfach weil der Compiler plötzlich aufhört, Cross-Function Optimierungen zu machen.
Für mich sieht ein
int f(int a, int b) throws c halt immer automatisch aus wie
void f(int* ret, c* ex, int a, int b)
Das macht übrigens auch die Rückgabe schneller als ein Rückgabewert. Aber der Compiler macht das meist schon alleine, und muss es in einigen Situationen auch tun nach dem neuen C++ Standard. Jai macht das auch so, weil schneller.
Nachteil ist aber hier: mehr Argumente bedeutet ggf. mehr Register Usage beim Aufruf - und damit ggf. auch auslagern auf den Stack, was es dann wieder langsamer macht.
Also wie mans macht, es hilft alles nichts. Irgendwie muss man die Fehler mitbekommen und behandeln, die perfekte Lösung ist noch nicht gefunden :-)
https://www.open-std.org/jtc1/sc22/wg21 ... 1947r0.pdf
Da drin wurde der Sache schonmal auf den Grund gegangen. Ein paar interessante Auszüge:
Schemes that consider an exception throw as just an alternative return path was considered different: not C++ exception handling
=> Dummer Kommentar von mir: ÄÄh, warum? ;-P
simply demonstrating that a throw is slower than a return does not demonstrate a violation [of the zero-overhead principle]
=> returns sind also schneller
Alternative schemes based on “markers” in stack frames were tried early on, but their performance was deemed inferior
=> Tabellen-basierte Abarbeitung ist also relativ optimal für Exceptions ohne Rückgabewerte.
In Summe ist das soweit ich weiß immer noch Stand der Dinge.
Siehe auch hier der Talk von Herb Sutter:
https://www.youtube.com/watch?v=ARYP83yNAWk&t=1200s
Wenn ich also eine Programmier-Sprache entwerfen würde, dann würde ich die schnellste aktuell bekannte Methode verwenden, die auch die Compiler noch gut optimieren -> Rückgabewerte (bzw. mit Syntactic Sugar).
Natürlich kannst du auch neue Wege gehen - im Paper steht auch dass es für den tabellenbasierten Ansatz durchaus noch Optimierungspotenzial gibt.
Würde mich auf jeden Fall interessieren wie es am Ende bei dir aussieht :-)
Zu den versteckten Kosten auch, finde ich super spannend was du da findest :-)
Mal schauen ob ich die Diskussion hierher verlegen kann.
Danke auf jeden Fall für den Link; sollte ich vielleicht auch nochmal spezifisch drauf eingehen. Irgendwie isses aber zu warm um klar zu denken, was zu schreiben oder weiter zu programmieren. Für Haus mit Klimaanlage verdiene ich zu wenig :-/
Also erstmal, weil ich's hier noch nicht verlinkt habe:
https://medium.com/@feldentm/designing- ... b54a550e7a
Mein größtes Problem ist tatsächlich erstmal das Memorymanagement, weil ich sicher nicht C++ RTTI oder RAII implementieren werde.
Hatte so schon drei drafts rumliegen, vermutlich schreibe ich aber erstmal dazu einen Kommentar, weil ich weniger drüber nachdenken muss und man direkt was spannendes sagen kann und mir auch bei meinen Begründungen Zusammenhänge klar geworden sind, die ich sonst vermutlich nicht kommuniziert hätte. Für mich ist C++-ABI-Kompatibilität natürlich kein Ziel. Mandat für mich sind derzeit POSIX, ELF, DWARF. Ob ich Itanium ABI EH nehme oder das wirklich clang oder gcc kompatibel mache entscheide ich später. Zumal ich demnächst vermutlich den vierten Ansatz implementiere, weil die cross function optimization tatsächlich für mich was kritisches ist. Habe einen
Test dazu geschrieben und wenn ich's nicht optimiert bekomme, sobald da ein finally in der Standardbibliothek steht muss ich mir deutlich überlegen, ob ich nicht aufgebe. In der Größenordnung wie ich das in Tyr machen will, könnte man das in C++ höchstens als LTO implementieren. Bin mir aber nicht sicher ob man das da bezahlen will, weil ich erwarten würde, dass man die erforderlichen Informationen nochmal herleiten müsste.
Zu deinen Fragen:
1) Weil das Go-style errors sind, sich das wie in Go verhält, mit struct schon immer ging und ein komplett anderes ABI hat.
2) Return ist erstmal erheblich schneller. Goto auch. Das ist nicht der Punkt; er schreibt eigentlich ganz passende Fragen auf, die man sich an der Stelle stellen muss. Für mich, insbesondere aus der Praxis, spannend, ist die Frage nach Wurfdistanz und Wahrscheinlichkeit. Wenn du eine Konfigurationskonsistenzprüfung hast, dann ist die Wahrscheinlichkeit quasi null, du undwindest vermutlich den Großteil deines Stacks. Sein bad_alloc Beispiel finde ich auch passend. Für das allermeiste was ich in meinem Leben gesehen habe ist es vollkommen ok so einen Java-style stacktrace in dem Fall zu haben und sich dann zu überlegen, was schiefgegangen ist. Kenne auch Ecken, wo man das fangen will. Selbst wenn man das fängt und einfach durch Lastabwurf löst ist es extrem unwahrscheinlich. Da die Chance vermutlich <1:1mio. Da willst du nicht aus jedem new einen Fehlerwert rausbekommen, falls es vielleicht nicht gut lief und erst recht nicht manuell bis ans Workpackagemanagement unwinden.
3) Nein, da geht's denke ich um was anderes. Du könntest Handleraddressen in deinen Stackframe packen. Das hat aber wieder verteilte Kosten im Gutfall.
Meine persönliche Position wäre ehrlich gesagt nicht zu sagen, dass man die 1:100 kassiert, sondern eher 1:1mio. Würde früher oder später in meine Tests auch ein nothrow reinbauen, was im Prinzip deaktiviertes Exceptionhandling wäre. Vielleicht unter anderem Namen. Ich meine, 1:100 würde bedeuten, dass man sich bei 'nem Mittelgroßen ArrayBuffer einfach den Rangecheck schenkt und auf die Exception beim Überschreiten des Rands hofft. WTF?
Wo's unklarer wird ist sowas wie JSON deserialisieren, dass man von irgendwoher bekommt. Wäre meine Lebenserfahrung aber auch eher, dass man voll auf den Gutfall optimieren will. Das meiste ist dann doch Maschinenkommunikation die das richtig macht. Momentan wäre mir aber nicht mal klar, wie man das seriös benchmarkt. Zumal ich eine local throw optimization habe und das binder inlining; vermutlich muss man ne breite Menge an Fällen benchmarken und hoffen, dass man am Ende irgendwas draus schließen kann.