Performance klassischer Patterns und Polymorphie

Design Patterns, Erklärungen zu Algorithmen, Optimierung, Softwarearchitektur
Forumsregeln
Wenn das Problem mit einer Programmiersprache direkt zusammenhängt, bitte HIER posten.
Antworten
Eisflamme
Establishment
Beiträge: 412
Registriert: 26.05.2002, 17:42
Wohnort: Köln

Performance klassischer Patterns und Polymorphie

Beitrag von Eisflamme »

Hi,

Ich wollte mich nur Mal kurz erkundigen, ob es auch in Spielen angebracht ist, die ueblichen Patterns zu benutzen, die oft einen Haufen Polymorphie voraussetzen. Auf Grund der Aufloesung des vtables oder so soll das irgendwie langsam sein, habe ich Mal gehoert. Auch habe ich das Mal selbst ausprobiert und hatte das Gefuehl, es hat mein Programm verlangsamt. Jetzt bin ich aber nicht sicher, ob das evtl. Quatsch ist oder bei mehreren tausend Objekten eine Rolle spielt.

Ich ueberlege z.B. ach so ein komplexes Listener-Pattern fuer meine Kamera-Objekte usw. zu verwenden, bin da aber zur Zeit noch vorsichtig, alles komplett OOP durchzudesignen, oder ob ich nicht ein paar "unschoene" Kompromisse eingehen muss.

Die Fragestellung ist superallgemein. Wenn euch dazu so nichts einfaellt, frage ich einfach Mal, ob bei modernen Engines viel mit so Patterns gearbeitet wird? Oder sind die gar bloedsinnig?

Ich finde halt bei irgendwelchen 3D-Tutorials fast nie so etwas. Gut, die basieren auch oft auf C, aber man koennte doch so schicke Dinge mit OOP anstellen, gibt es triftige Gegenargumente?

Viele Gruesse,
Eisflammor
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4273
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Chromanoid »

jgl hat im Artikel-Thema einen Link diesbezüglich hinzugefügt: http://www.gotoandplay.it/_articles/200 ... tterns.php

Ich denke das Listener Pattern solltest du bedenkenlos einsetzen können... Bei Spielen wurde in der Vergangenheit imo wahrscheinlich vor allem aus Unwissen oder Performance Gründen auf solche Patterns verzichtet, aber ich denke mit der Leistung heutiger Rechner sollte man sich da nicht einschränken müssen - hauptsache Produktivität.
Despotist
Establishment
Beiträge: 394
Registriert: 19.02.2008, 16:33

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Despotist »

Eisflamme hat geschrieben: Ich finde halt bei irgendwelchen 3D-Tutorials fast nie so etwas. Gut, die basieren auch oft auf C, aber man koennte doch so schicke Dinge mit OOP anstellen, gibt es triftige Gegenargumente?
Das liegt denke ich daran dass in Tutorials eine Technologie demonstriert werden soll und daher alles was nicht unmittelbar dafür erforderlich ist weggelassen wird. Und da Tutorials eher für Anfänger gedacht sind würden zwei komplexe Konzepte diese sicher überfordern weil sie kaum noch verstehen was warum gemacht wird und was die einzelnen Komponenten bewirken.

Generell sollen die Patterns das Design leichter erweiterbar, wartbar und für andere verständlich machen. Wenn du allein an einem Projekt sitzt dessen Anforderungen sehr klar umrissen sind ist es denke ich nur zu Übungszewecken angebracht sowas einzusetzen. Einfach weil der Aufwand, die Schreibarbeit und das Testen durch Patterns erhöht werden. Erst ab einer gewissen Gesamtkomplexität wird die Entwicklungszeit die du durch Patterns einsparst von der Zeit die du für die Implementierung brauchst übertroffen und erst dann sollten mMn Patterns eingesetzt werden. Andererseits ist es auch nicht verkehrt sich eine saubere Arbeitsweise von Anfang an anzugewöhnen aber man läuft eben Gefahr dass bei kleineren Projekten mit Kanonen auf Spatzen geschossen wird und der Code am Ende zum großen Teil aus "Schnickschnack" besteht.

Über was ich mir am wenigsten Gedanken mache ist Rechenzeit. Bei heutigen und zukünftigen Computern sollte man im Hobbybereich nicht an die Grenzen stoßen. Wenn man wirklich einen Flaschenhals hat kann man profilen und den durch andere Algorithmen beheben aber das Design sollte da keine Probleme bereiten. Die Patterns dienen rein zur Verbesserung der Entwicklungszeit und der Endnutzer bekommt davon außer (hoffentlicht) kleiner Fehlerquote nichts mit.

Außerdem ist Pattern nicht gleich Pattern da man viele permanent "on the fly" einsetzt (Singleton) und daher nicht sauber zwischen "Patterns verwenden" und "Patterns nicht verwenden" getrennt werden kann. Man verwendet einfach die die angebracht erscheinen und nicht mehr.
Eisflamme
Establishment
Beiträge: 412
Registriert: 26.05.2002, 17:42
Wohnort: Köln

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Eisflamme »

Okay, das klingt schon Mal gut. Den Artikel werde ich auch noch durchlesen.

Was ich im Kopf habe ist halt die klassische Map/List über ein abstraktes Objekt, wo man dann in jedem Renderdurchgang polymorph eine Methode aufruft. Ist gar nicht Mal son typisches Pattern, glaube ich. Wie auch immer, da kann man sich ja auch eigenes zusammenbasteln. Muss man da auf Performance achten oder kann man vererben und polymorph arbeiten, wie man lustig ist? Macht die vtable Probleme?
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4273
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Chromanoid »

also komplexe polymorphe strukturen würde ich insgesamt nicht so empfehlen... tiefe vererbungshierarchien sind generell eher unschön.
Ich würde gerade bei spielen eher komponentenbasiert arbeiten: z.b. http://www.josericardojunior.com/docs/D ... BG08_1.pdf
bei den game programming gems büchern ist auch in einigen was dazu dabei... Das Stichwort "Aspektorientierte Programmierung" (AOP) könnte auch Hinweise bringen. Wenn du dich da schlau machen solltest und gute Artikel findest, kannst du sie ja mal im Ressourcen Thread verlinken und hier mal berichten wie es dir so ergangen ist... :)
Benutzeravatar
jgl
Establishment
Beiträge: 109
Registriert: 08.04.2009, 08:58

Re: Performance klassischer Patterns und Polymorphie

Beitrag von jgl »

[Etwas Offtopic]
Das Stichwort "Aspektorientierte Programmierung" (AOP) könnte auch Hinweise bringen. Wenn du dich da schlau machen solltest und gute Artikel findest, kannst du sie ja mal im Ressourcen Thread verlinken und hier mal berichten wie es dir so ergangen ist... :)
Bin ich froh, das dass mal jemand anspricht. Ich habe darüber mal etwas gelesen, und wollte mich da etwas genauer beschäftigen, da das VLT was bringen könnte. Was ich bis jetzt gelesen habe, ist ein Verwendungsgebiet Logging z.B..
Der Aufwand dies zu verwenden ist allerdings ziemlich hoch (über Methadaten unso...)
Dann gibts danoch AspectC++, das ist sozusagen ein PreProcessor, oder soetwas.
Man kann dieses AspectC++ auch frei verwenden (LGPL)!
Wie gesagt, Ich wollte mich dazu etwas informieren, aber die Zeit... die böse Zeit ... :(

Wenn wir gleichmal dabei sind:
http://wwwiti.cs.uni-magdeburg.de/iti_d ... /featurec/
[/Etwas Offtopic]
Eisflamme
Establishment
Beiträge: 412
Registriert: 26.05.2002, 17:42
Wohnort: Köln

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Eisflamme »

Ich bin jetzt nicht der mega Javafreund, aber da vererbt man doch auch tief und fast jedes Pattern mit Listenern etc. arbeitet mit Polymorphie. Ist der Stil da so anders oder sind das jetzt einfach performancespezifische Nachteile, die dazu führen, dass wir das in C++ nicht so konzipieren sollten?

Ich finde es ganz einfach besser erweiterbar, wenn ich gewisse Klassenabhängigkeiten habe und eine Art Listener-Konzept verwende, als wenn ich hart überall Zeiger reincode und damit statt Abhängigkeiten zu Interfaces welche zu Implementierungen habe, Mal ganz allgemein gesagt. Was ist denn mit obigem Beispiel der Polymorphie? Da würdest Du jetzt drauf verzichten? Wenn ich OOP nicht voll ausnutzen kann, ist das für mich aber irgendwie eine eingeschränkte Nutzung von C++, muss man das bei Spieleprogrammierung in Kauf nehmen?
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Krishty »

Mach das, was dir am einfachsten fällt … Despotist hat da recht. Wenn du es wirklich schaffen solltest, einen modernen CPU-Kern mit SpielLogik zum Flaschenhals zu machen, verneige ich mich vor dir – ich komme auch mit anderen Komponenten zusammen nicht über 10 % Auslastung … ich glaube, dass du deine Spiel- und Verwaltungslogik auch in einer echtzeitinterpretierten Skriptsprache schreiben könntest und zeitgemäße CPUs immernoch gähnen würden. Solche Sachen wie, bei animierten Texturen einfach nur einen Texturzeiger zu wechseln statt jeden Frame tausend Tiles zu aktualisieren, bringen letztendlich hundertmal mehr.

(Dennoch: Virtuelle Funktionen sind zu meiden, wo man sie nicht braucht. Allerdings bedeutet „nicht brauchen“: Wenn das Konzept nicht zwingend erfordert, dass die Funktion virtuell ist, mach sie nicht virtuell. Es bedeutet nicht, auf ein ansonsten schwächeres Konzept umzusteigen, nur, weil es keine virtuellen Funktionen benutzt.)

Vor tiefen VererbungsStrukturen braucht man theoretisch keine Angst zu haben … das meiste aus STL und boost dürfte zwischen fünf und 30 Basisklassen haben. Praktisch problematisch wird es nur, sobald implizit gecastet wird wo man es nicht will, oder man eine Funktion versehentlich nicht überschreibt (z.B. ein const an einem Paramter vergisst). Auch bei Vererbung gilt wieder: Wenn es das Konzept fordert, ohne Reue benutzen. In den restlichen Fällen spart Komposition Nerven, allerdings auf Kosten von Tipparbeit.

Die beste Optimierung ist übrigens ein schmales Feature-Set mit geringer Komplexität. Das impliziert auch, trivialen Problemen keine komplizierten Patterns aufzuzwingen.

Gruß, Ky
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4273
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Chromanoid »

Eisflamme hat geschrieben:und fast jedes Pattern mit Listenern etc. arbeitet mit Polymorphie.
Naja bei Java benutzt man i.d.R. Java Interfaces dafür, so benutzt man also nicht polymorphie, schließlich werden keine schon implementierten Methoden überschrieben das macht das ganze etwas handhabbarer als mehrfachvererbung... In Java gibt es im gegensatz zu c++ übrigens keine mehrfach vererbung, man geht also an die von dir angesprochenen patterns i.d.R. etwas anders heran (jedenfalls schließe ich das aus deinen ausführungen)...

edit: hab da mal was falsches durchgestrichen - interfaces können ja schon als aspekt der polymorphie gesehen werden (man kann ja eine variable vom typ interface xyz definieren, diese besitzt dann eine vielgestaltigkeit)... imo sind interfaces wesentlich sicherer und wartbarer als mehrfachvererbung.

btw. sollte man das listener pattern in c++ nicht eher mit methoden-zeigern implementieren?
zwergmulch
Beiträge: 91
Registriert: 07.12.2009, 16:42
Echter Name: Fabian R

Re: Performance klassischer Patterns und Polymorphie

Beitrag von zwergmulch »

bei methoden-zeigern kann man dann aber imo nicht auf methoden zeigen, sondern nur auf funktionen ausserhalb einer klasse (was den namen etwas unsinning macht)

edit: Ok, zwar möglich aber bringt nichts da man halt eine (Basis)Klasse bestimmen müsste, also im Prinzip nicht besser als virtuelle Funktionen.
Zuletzt geändert von zwergmulch am 06.09.2010, 15:30, insgesamt 1-mal geändert.
Bild
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Krishty »

(Ohne alles gelesen zu haben:)
Das nennt sich Pointer-to-member und hat die gleiche eklige Syntax wie Funktionszeiger, nur mit vorangestelltem Class-Scope (void (MyClass::*PtrName) (int) = MyClass::VoidFuncWithIntParam; MyInstance.*PtrName(1);).
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Biolunar
Establishment
Beiträge: 154
Registriert: 27.06.2005, 17:42
Alter Benutzername: dLoB

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Biolunar »

Sowas verwendet man ja auch nicht ;)
std::tr1::function oder boost::function machen diese hässlichen Zeiger um ein ganzes Stück schöner. Außerdem haben die den Vorteil, dass man sowohl normale Funktionszeiger, als auch Memberfunktionszeiger ins selbe Objekt stecken kann.
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4273
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Chromanoid »

aber man hat bei methodenzeigern in allen fällen für das listenerpattern doch wieder mehrfachvererbung nötig oder? somit würde mein voschlag eh ins wasserfallen... mmh naja vielleicht gibt es ja eine delegate implementierung für c++... ich finde das listener pattern einfach so schön bei c# und co. - einfach objekt.addListener(methode) und fertig...
Benutzeravatar
dv
Beiträge: 51
Registriert: 15.09.2002, 17:46
Benutzertext: Ugauga.
Alter Benutzername: dv
Wohnort: Südamerikanischer Dschungel
Kontaktdaten:

Re: Performance klassischer Patterns und Polymorphie

Beitrag von dv »

Chromanoid hat geschrieben:aber man hat bei methodenzeigern in allen fällen für das listenerpattern doch wieder mehrfachvererbung nötig oder? somit würde mein voschlag eh ins wasserfallen... mmh naja vielleicht gibt es ja eine delegate implementierung für c++... ich finde das listener pattern einfach so schön bei c# und co. - einfach objekt.addListener(methode) und fertig...
Siehe boost.signals2, sigslot, libsigc++. Eine simplere Alternative ist noch http://github.com/pbhogan/Signals .

An sich ist es nicht so schwer, sowas wie c#-Delegates selber zu coden (delegates nennt man in c++ üblicherweise signals). So ein Signal ist nix weiter wie eine Liste von Callbacks, und beim Signalaufruf werden alle Callbacks da drinnen aufgerufen. Es gibt einige Signal-C++-Libraries, die erfordern, dass man von einer Basisklasse erbt - Finger weg, so ein Erben ist unnötig, es geht auch ohne.
Eisflamme
Establishment
Beiträge: 412
Registriert: 26.05.2002, 17:42
Wohnort: Köln

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Eisflamme »

Okay, Performance ist kein Thema mehr, dann kommen wir meinetwegen gern zum Listener-Pattern.

signal sieht ja interessant aus. boost::signal2 und diese letzte, einfache lib gefallen mir auch sehr gut.

Ich hab das jetzt so wie in Java implementiert: Meine Camera erbt von CameraSender (das nennt man anders, aber ist mir gerade nicht eingefallen) und CameraSender hat halt addListener(), removeListener() und notifyWhatever() und speichert eine Liste auf CameraListener. CameraListener hat nur eine abstrakte, protected Methode whatever() (hier also cameraChanged oder so). Und mein ViewableModel erbt von CameraListener und implementiert whatever(). Und wenn sich eine Kamera aendert, wird notifyWhatever() in meiner Kameraklasse aufgerufen, was alle whatever()s der eingetragenen Listeners aufruft. Natuerlich muss mein ViewableModel die aktive Kamera kennen und sich dort registrieren, aber das ist bei mir jetzt gerade kein Problem. Vorteil ist dafuer, dass sich jeder als CameraSender und jeder als CameraListener anmelden kann. Eigentlich macht Ersteres keinen Sinn, daher koennte man CameraSender und Camera auch zusammenfuehren, aber ich nutze wie gesagt so einen CameraManager... und da man Cameras ja auch ausserhalb davon benutzen kann, koennte es auch andere Sender geben.

Kann davon auch Mal ein UML posten spaeter.

Man sieht an den zwei Beispielen, wie unterschiedlich man Java und C++ eigentlich auffassen muss. Die boost-Varianten nutzen die schlichte Eleganz von C++ einfach aus, die Java nicht bieten kann, weil es zu systemfern ist (keine Funktionszeiger). Jetzt frage ich mich aber, wo genau liegt der Vorteil in den signal-Varianten ggue. einer so ausfuehrlichen Klassenimplementierung wie man sie in Java machen wuerde? Einfach die Schlankheit und Eleganz und dass man weniger Klassen braucht und die Verknuepfungen schneller sieht?

Ich glaube, ich muss mich Mal komplett durch boost graben... Ich weiss, haette ich schon laengst tun sollen, aber wenn das mit einem anderen Stil einhergeht, der in C++ der Aufassung "erfahrener Leute" schoener/angemessener ist, begehe ich ja laufend Schoenheitsfehler! :cry: Und dann muss man Java und C++ eigentlich auch auf dieser Basis vergleichen, alles andere ist ja fast schon Entartung der Programmiersprachen.
Zuletzt geändert von Eisflamme am 03.09.2010, 02:17, insgesamt 3-mal geändert.
Benutzeravatar
dv
Beiträge: 51
Registriert: 15.09.2002, 17:46
Benutzertext: Ugauga.
Alter Benutzername: dv
Wohnort: Südamerikanischer Dschungel
Kontaktdaten:

Re: Performance klassischer Patterns und Polymorphie

Beitrag von dv »

Natürlich sind auch Methodenzeiger möglich. Es müssen auch keine statischen Methoden sein - man muss nur den Pointer auf das Objekt mitliefern.

An dieser Stelle ist es wichtig, sich mal function objects anzusehen, implementiert in boost.function und tr1 function. Zusammen mit tr1 bind, boost lambda bind, oder boost phoenix bind ein extremst mächtiges Werkzeug. Beispiel:

Code: Alles auswählen

void foo(int x, float y) {}
class X { void bar(string const &str, int i) {} };
class Functor { typedef int result_type;   int operator()(float x) { return 42; } };

X x;
Functor functor;
function < void(int a, float b) > funcobj1 = bind(&foo, _1, _2);
funcobj1(5, 6); // ruft foo(5, 6) auf

function < void(string const &str, int i) > funcobj2 = bind(&X::bar, &x, _1, _2);
funcobj2("hello world", 1); // ruft x.bar("hello world", 1) auf

function < void(int i) > funcobj3 = bind(&X::bar, &x, "somefixedvalue", _1); // der 1. Parameter wird quasi fixiert; analog zum "Currying" bei funktionalen Sprachen
funcobj3(11); // ruft x.bar("somefixedvalue", 11) auf

function < int(float f) > funcobj4 = bind(&functor, _1);
funcobj4(4.1f); // ruft functor(4.1f) auf
_1 und _2 sind Platzhalter für Parameter (bei phoenix heissen sie arg1 und arg2). function < > sind Function Objects. Wie man sieht, stellen sie eine generische Lösung zur Referenzierung auf Funktionen bzw. Funktoren dar. bind() gibt ein Function Object zurück.

Auf dieser Basis gestellt, werden signals deutlich einfacher. Hier ein boost.signals2 Beispiel:

Code: Alles auswählen

void foo(string const &str) { cout << str << endl; }
struct X { void bar(string const &str) { cout << str << endl; } };

X x;
signal < void(string const &somestr) > mysignal;
mysignal.connect(bind(&foo, _1));
mysignal.connect(bind(&X::bar, &x, _1));

mysignal("Hello world");
Eisflamme
Establishment
Beiträge: 412
Registriert: 26.05.2002, 17:42
Wohnort: Köln

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Eisflamme »

Okay, jetzt verstehe ich. Dann ist das super :)

Hat jemand noch Antworten auf meine anderen Frage zum Vergleich der vererbungsbedingten Loesung vs. der signal-Loesung?
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4273
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Chromanoid »

Naja bei Java benutzt man bei Listenern ja nicht selten anonyme Klassen. Und an sich ist das mit Interfaces dann doch recht viel Schreibarbeit.
Wenn man Closures/Signals/Delegates benutzt, kann man sich viel Schreibarbeit sparen und mehrere Subjekte, die die gleichen Listener brauchen, auch ohne anonyme Klassen o.Ä. durch eine einzige Klasse leicht abhören.
Man könnte zwar über einen Sender-Parameter die Verarbeitung durch eine einzige Listener-Funktion ermöglichen, aber mit delegates/signals/closures ist das gar nicht nötig, weil es durch mehrere Listener-Funktionen für unterschiedliche Subjekte übersichtlicher wird und leicht machbar ist.
Arbeitet man in Java mit Swing und benutzt den GUI Editor Matisse aus Netbeans, wird dieses Verhalten übrigens simuliert, indem automatisch für Listener (zum Beispiel ActionListener) eine anonyme Listener-Klasse im GUI Code generiert wird, die dann eine Funktion der Container-Klasse aufgeruft. Als Entwickler hat man es dann also nur mit diesen Funktionen in der Container-Klasse zu tun für deren Ausführung durch die generierten anonymen Klassen gesorgt wird. Dies sorgt für Übersicht und einfache Handhabe und wäre mit Delegates/Signals/Closures durch wesentlich weniger generierten Code möglich...
Eisflamme
Establishment
Beiträge: 412
Registriert: 26.05.2002, 17:42
Wohnort: Köln

Re: Performance klassischer Patterns und Polymorphie

Beitrag von Eisflamme »

Ok, also die klassenträchtige Variante nur die am einfachsten zu verstehende, die mit Eclipse ohne Extrazeug klappt und daher wird die allen gezeigt und im GoF-Buch so breit getreten (hab's nie gelesen) :) Sehr gut, dann auf zu Signals!
Antworten