Wieviel Performance kostet die Objekt orientierte Programmierung?
-
Welche Tatsachen? Die Klasse heißt A und die einzige Membervariable "value_", es existiert einfach kein Umfeld. Ist std::vector jetzt auch schlecht gekapselt? Das Beispiel sollte nur verdeutlichen, dass Methode sowie Referenz wegoptimiert werden, und das hat es auch geleistet. Dass ohne Referenz nicht plötzlich weniger optimiert wird ist doch klar.
-
cooky451 schrieb:
Welche Tatsachen? Die Klasse heißt A und die einzige Membervariable "value_", es existiert einfach kein Umfeld.
Die (falsche) Tatsache "hier ist was gekapselt".
Ein Member einer Klasse das "private" ist, impliziert, dass es nicht uneingeschränkt von ausserhalb verwendbar ist -- wieso sollte man es sonst "private" machen und nicht gleich "public"?
Sobald du eine mutable Referenz zurückgibst, ist das aber nicht mehr der Fall, denn dann ist das "private" Member auf einmal uneingeschränkt von ausserhalb verwendbar.Ergo: der Kuchen ist eine Lüge.
Ist std::vector jetzt auch schlecht gekapselt?
Nein. Bietet denn std::vector Referenzen auf seine Member der über Referenz-Getter an? Nein, tut er nicht. Sonst könnte man
vec.size() = 42;
oder ähnlich gefährlichen Unsinn schreiben.Das Beispiel sollte nur verdeutlichen, dass Methode sowie Referenz wegoptimiert werden, und das hat es auch geleistet. Dass ohne Referenz nicht plötzlich weniger optimiert wird ist doch klar.
Ein Beispiel das man in Gedanken erstmal abändern muss, und dann Vermutungen darüber anstellen ob die abgeänderte Version sich auch noch so verhält wie die gezeigte, ist irgendwie kein gutes Beispiel.
-
hustbaer schrieb:
Ergo: der Kuchen ist eine Lüge.
Ne, es gibt nämlich keinen Kuchen. Der einzige Sinn der Funktion war ja, dass sie wegoptimiert wird.
hustbaer schrieb:
Nein. Bietet denn std::vector Referenzen auf seine Member der über Referenz-Getter an? Nein, tut er nicht.
operator [] ?
hustbaer schrieb:
Ein Beispiel das man in Gedanken erstmal abändern muss, und dann Vermutungen darüber anstellen ob die abgeänderte Version sich auch noch so verhält wie die gezeigte, ist irgendwie kein gutes Beispiel.
So und hier kommen wir wohl zum einzig relevanten Teil der Diskussion. Und nein, ich denke das Beispiel war gut, weil es zeigt, dass Zugriffe über Methoden auch dann komplett wegopimiert werden, wenn man mit non-const Referenzen arbeitet. (Was z.B. beim std::vector nicht ganz unwichtig ist.)
-
cooky451 schrieb:
hustbaer schrieb:
Nein. Bietet denn std::vector Referenzen auf seine Member der über Referenz-Getter an? Nein, tut er nicht.
operator [] ?
Manchmal fällt es mir wirklich schwer freundlich zu bleiben, da ich mich des Eindrucks nicht erwähren kann verarscht zu werden.
Aber gut.
Welche Member werden denn über operator [] zugänglich gemacht?
Des weiteren halte ich das Beispiel immer noch für Unsinn - warum habe ich ja schon beschrieben, ich werde nicht nochmals darauf eingehen.
-
cooky451 schrieb:
Pfusch schrieb:
Wieso heftest du den Adressoperator an den Funktionsnamen und nicht an den Rückgabetyp, so wie es IMO sein sollte?
Deswegen: http://ideone.com/NGNYG
Ich würde Referenzen und Pointer auch lieber dem Datentypen zuschreiben. Das Komitee selbst scheint sich ja nicht mal ganz einig über die Schreibweise zu sein. Früher standen */& beim Variablennamen, jetzt stehen sie öfter beim Typ. Einheitlich machen sie es jedenfalls nicht, von daher zählt für mich vor allem die Eigenschaft von oben.Das ist aber keine Begründung.
Und im obigen Falle steht dein & nicht beim Variablennamen, sondern beim Namen der Funktion. Das ist etwas völlig anderes.Bei so Sachen, wo Datentyp und Variablennamen in einer Zeile stehen,
z.b. int &i..., da ist es vielleicht egal, ob der Adressoperator nun beim int oder beim Variablennamen i steht, aber in deinem Fall steht er bei einem Funktionsnamen, der absolut nichts mit dem Rückgabetyp zu tun hat.
Insofern ist der Code also Irreführend und so mancher Newb läuft Gefahr, das mit Funktionszeigern zu verwechseln, wenn er sich bei der Bedeutung von & und * noch nicht sicher ist.
-
Pfusch schrieb:
Deswegen gibt es das Prinzip der Datenkapselung und dem Geheimnisprinzip, sowie der maximalen Entkopplung.
Dein Code ist also zwar vielleicht schnell, aber er erfüllt die Bedingungen der OOP nicht mehr.
Da kann man das ganze Klassenkonstrukt auch gleich weglassen.Ja, die die glauben, dass OO nichts kostet, glauben auch, dass alles objektorietiert wird, wenn man class darum schreibt. Dass der Performance verlust nicht von ein paar Gettern kommt, sondern auf ein anderes Design zurückzuführen ist, haben sie noch nicht durchschaut. Vorallem durch Information Hiding (Geheimnisprinzip) und Entkopplung entsteht anderes Design und anderer Code. Wenn man sich keine sauberen allgemeinen Schnittstellen zwischen den Klassen definiert, sondern Schnittstellen, die nur funktionieren, wenn man den Inhalt der Klasse nicht ändert, dürfte es keinen Performanceunterschied geben. Dann hat man aber auch die ganzen Vorteile von OO, wie Erweiterbarkeit, nicht mehr. Ich will aber nicht empfehlen, wegen der Performance nicht OO zu programmieren, die Vorteile von OO überwiegen deutlich. Vor allem wenn man nicht allein programmiert ist es gut, wenn andere deine Klassen verwenden können, ohne wissen zu müssen was intern passiert.
-
@Herausforderer
Du willst hier mit einer bestimmten Vorstellung von OO ein bestimmtes Design "erzwingen", das erstmal viel flexibler ist als der von dir gezeigte prozedurale Code, und (Vermutung/Unterstellung) auch Dinge wie Laufzeitpolymorphie verwendet.Dass das nicht mehr so schnell sein wird, wie der (einfachere, weniger flexible) prozedurale Code, ist sehr wahrscheinlich. Keine grosse Überraschung.
Bloss dass deine Vorstellung von OO nicht unbedingt meiner Vorstellung von OO entspricht.
-
hustbaer schrieb:
Welche Member werden denn über operator [] zugänglich gemacht?
Elemente des vektor internen Felds? Oder sind Elemente eines Felds keine Member mehr? Ich kann über die Referenz auch den Vektor kaputt machen: ich lösche das mit placement new erzeugte Element einfach. Damit ist die Rückgabe der Referenz des Elements genauso schlimm wie
vec.size()=42;
-
hustbaer schrieb:
@Herausforderer
Du willst hier mit einer bestimmten Vorstellung von OO ein bestimmtes Design "erzwingen", das erstmal viel flexibler ist als der von dir gezeigte prozedurale Code, und (Vermutung/Unterstellung) auch Dinge wie Laufzeitpolymorphie verwendet.Nein nicht nur Laufzeitpolymorphie, man hackt nur nicht einfach Abhängigkeiten zwischen Klassen rein bei denen man das Wissen über den internen Aufbau von Datenstrukturen einer anderen Klasse ausnutzt um irgendwas schneller zu machen. OK, viele machen es mehr oder weniger direkt doch und wundern sich dann, dass plötzlich soviel anderes noch angepasst werden muss, wenn man eine Klasse ändert.
-
Herausforderer schrieb:
Objektorientiert programmieren bedeutet ja nicht, nur ein paar Getter und Setter für meine Arrays zu machen, sondern ein Klassendesign mit Kapselung zu haben und OO Pattern zu verwenden, also z.B. Observer für Schuss-Events oder ähnliches verwenden.
Pattern bei 40 Zeilen relevantem Code zu verwenden, ist ziemlicher Overkill, meinst du nicht? Das hat dann auch nichts mehr mit guten OO-Design zu tun.
Zufällig muss ich diesen Quatsch gerade auch für die Uni machen, also auf Teufel komm raus Design Patterns verwenden, die ich eigentlich nicht nehmen möchte.
-
hustbaer schrieb:
cooky451 schrieb:
Welche Tatsachen? Die Klasse heißt A und die einzige Membervariable "value_", es existiert einfach kein Umfeld.
Die (falsche) Tatsache "hier ist was gekapselt".
Ein Member einer Klasse das "private" ist, impliziert, dass es nicht uneingeschränkt von ausserhalb verwendbar ist -- wieso sollte man es sonst "private" machen und nicht gleich "public"?
Z.B. bei einer Vektor3D-Klasse würde ich auch vielleicht privat
float x, y, z
benutzen und diese über Referenzgetter zugänglich machen. Der Grund ist, dass ich so die Implementierung z.B. leicht auf ein Array umstellen kann ohnen die Schnittstelle zu verändern. Auf der anderen Seite sind Referenzen aber kein Problem, da mich eine Änderung seitens des Benutzers nicht interessiert (es gibt keine verletzbaren Invarianten). Und wenn cooky451s Klasse lediglich einenfloat
kapseln soll, würde ich das genauso legitim halten. Das hat mitsize
beimvector
nichts zu tun.
-
Michael E. schrieb:
Herausforderer schrieb:
Objektorientiert programmieren bedeutet ja nicht, nur ein paar Getter und Setter für meine Arrays zu machen, sondern ein Klassendesign mit Kapselung zu haben und OO Pattern zu verwenden, also z.B. Observer für Schuss-Events oder ähnliches verwenden.
Pattern bei 40 Zeilen relevantem Code zu verwenden, ist ziemlicher Overkill, meinst du nicht? Das hat dann auch nichts mehr mit guten OO-Design zu tun.
Ja, das Beispiel ist zu klein.
-
otze schrieb:
hustbaer schrieb:
Welche Member werden denn über operator [] zugänglich gemacht?
Elemente des vektor internen Felds? Oder sind Elemente eines Felds keine Member mehr?
Natürlich nicht, waren sie auch nie.
Ich kann über die Referenz auch den Vektor kaputt machen: ich lösche das mit placement new erzeugte Element einfach. Damit ist die Rückgabe der Referenz des Elements genauso schlimm wie vec.size()=42;
"Genau so schlimm" kann man in C++ immer erreichen, man kann immer
memset(&obj, 123, sizeof(obj))
machen, das wird auch kaum ein Objekt "überleben".
-
Ziel von C++ ist es, nur "zero cost abstractions" anzubieten, also Abstraktionen, die im Vergleich zu per Hand nachgebautem C-Gefrickel performanztechnisch nicht schlechter darstehen.
Es hat ja noch gar keiner den "C++ Performance Report" ausgepackt, der gerade dazu da ist, diese Performanz-Mythen bzgl C++ aus der Welt zu räumen. Dann mach ich das mal:
http://www.open-std.org/jtc1/sc22/wg21/docs/TR18015.pdf
-
Wieviel Performance kostet die Objekt orientierte Programmierung?
Im Vergleich zu was?
-
krümelkacker schrieb:
Ziel von C++ ist es, nur "zero cost abstractions" anzubieten, also Abstraktionen, die im Vergleich zu per Hand nachgebautem C-Gefrickel performanztechnisch nicht schlechter darstehen.
Es hat ja noch gar keiner den "C++ Performance Report" ausgepackt, der gerade dazu da ist, diese Performanz-Mythen bzgl C++ aus der Welt zu räumen. Dann mach ich das mal:
http://www.open-std.org/jtc1/sc22/wg21/docs/TR18015.pdfWow, danke! Das ist sicherlich von unschätzbarem Wert für mich, wenn ich mal C++ lerne!
-
hustbaer schrieb:
otze schrieb:
hustbaer schrieb:
Welche Member werden denn über operator [] zugänglich gemacht?
Elemente des vektor internen Felds? Oder sind Elemente eines Felds keine Member mehr?
Natürlich nicht, waren sie auch nie.
Sicher sind es Member. Nur eben keine die man kapseln müsste.
Wenn ich einen vector serialisiere, dann will ich dass er den Inhalt seines Feldes mit-serialisiert. Es ist also Teil der Klasse und dazu sagen wir nunmal Member.
Ist auch übrigens leicht an dem Variablen Namen zu erkennen den es in der Klassendefinition hat.
Nur gilt hier die übliche Kapselung von wegen kein Zugriff von aussen nicht, weil es uninteressant ist welchen Wert es dort gibt. Der vector will den Zugriff ja erlauben. Das ist auch vollkommen OK. Man darf Member haben die alle Werte annehmen können.
Vielleicht besser ersichtlich bei std::array? Da ist ein T arr[NUM]; Member vorhanden. Und auf diesen kann ich zugreifen wie ich lustig bin. Aber das ist by Design so.
cooky451 hätte keine Referenz zurück geben sollen, aber das bedeutet nicht dass sein Beispiel schlecht war. Es war genau richtig. Eine Diskussion darüber halte ich für fragwürdig. Es geht doch nur um Prinzipien. Keine Referenzen nach aussen leaken lassen ist eine super Regel. Sie stimmt wie alle Regeln nahezu immer. Aber wie alle Regeln gibt es eben Situationen wo sie nicht stimmt.
zB bei Containern (wozu zB auch shared_ptr zählt).
-
ich habe ein paar studenten an der uni in Utah mal geholfen das anhand eines raytracers herauszufinden. man durfte die sprachen seiner wahl nutzen und das resultierende bild sowie die source daten waren vorgegeben (Standford Bunny mit 5k polys und Utah Teapot mit 500polys).
ich glaube wir hatten gcc 3.4 und VS2003 benutzt. ich war an c++ und c dran, das resultat war, dass c++/oop (also z.b. vector klassen mit ueberladenen operatoren) ca 5% langsammer war als c/prozedural(z.b. vec_dot_3f(out,va,vb);). (java war ca 230% langsammer und c# 400% langsammer wenn ich mich recht erinnere).
vom assembler code (aus VS2003) her sah ich, dass einige ueberladene operatoren sich nicht dazu bringen lassen wollten ohne temp copy auszukommen, selbst nach dem inlinen, ansonsten sah der assembler ziemlich gleich aus. (das war simples brute force raytracing damit feinheiten von implementierungen besserer algorithmen keine rollen spielen, z.b. ob man beim kd-tree mit < oder <= vergleicht.)ich habe mir den java bytecode angeschaut, das sah sehr unoptimiert aus, vermutlich weil es auch egal ist, da erst der JIT den finalen code erstellen soll und dessen output sah ich nicht).
c# war ziemlich.. emm.. overengineered, der programmierer hat z.b. fuer die vector klasse, die er hlsl nachempfunden hat, alle permutationen von zwizzling im vector als accessors generieren lassen (da gab es wohl ein extra tool fuer bei c#).
ich habe ein paar jahre spaeter das gesehen: http://www.flipcode.com/archives/Faster_Vector_Math_Using_Templates.shtml und es fuer diesen test implementiert, das war dann um einiges schneller als prozedural, ich glaube ca 20%.
es haengt also davon ab wie man seine werkzeuge nutzt.
aber es gibt ja die regel das 90% der laufzeit im 10% vom code verbracht wird, wenn man also effektiv 90% vom code herstellen kann, dank OOP, hat man am ende vielleicht mehr zeit die 10% genau zu optimieren.
Ich kenne einige leute die dann fuer die kritischen funktionen den assembler vom compiler nehmen und ihn fuer die ziel cpu optimieren, gerade bei sowas wie Atom, PPU, SPU, ARM-NEON kann man durch besseres instruction scheduling oder bessere wahl von instructions fuer eine gleichmaessige auslastung der pipelines, locker die doppelte performance rausholen (ich weiss nicht weshalb die compiler bei sowas so unglaublich versagen, wenn man sonst sieht, dass sie manchmal 9kb binary replizieren um _ein_ compare+branch zu sparen).
-
rapsos Posting uebersetzt: "Ich bin der geilste"
-
rapso schrieb:
vom assembler code (aus VS2003) her sah ich, dass einige ueberladene operatoren sich nicht dazu bringen lassen wollten ohne temp copy auszukommen, selbst nach dem inlinen, ansonsten sah der assembler ziemlich gleich aus.
Kann man das mit dem &&-Opertator aus C++11 nicht schneller hinkriegen?