Wie internen Zustand von Objekt auslagern?
-
Edit: Moment, da fehlt ein virtual.

Edit2: OkayWas spricht dagegen?
class A { public: virtual Result baz(B& b, Arg& arg) { Zustand zustand; return bar(zustand, b.transform(foo(zustand, arg))); } private: virtual Result foo(Zustand& zustand, Arg& arg); // static? virtual Result bar(const Zustand& zustand, Result r); // static? };
-
knivil schrieb:
Not every problem is an object, dude.
Fuer Prallelisierung eignen sich in erster Linie zustandslose/freie Funktionen.
Was ja meinem Problem entspricht: wie kriege ich das Ding Zustandsfrei?. Das es diesen Zustand gibt, ist ein blödes Implementationsdetail, weil wir gerne effizienten Code haben wollen und deswegen nicht Ergebnisse großer Matrix-Matrix Multiplikationen weg werfen wollen. (oder im Falle von günstigen Funktionen ein Skalarprodukt oder das Ergebnis eines exp-Aufrufs).
schnelle Lösung ... für alles offen
Das widerspricht sich.
Nur, wenn man es drauf anlegt, dass es sich widerspricht. Ich bin erst mal für jede mögliche Lösung offen, würde dann aber Benchmarken, wie teuer sie ist.
@cookie Es gibt verschiedene Versionen von myFunc, die sich da in Kleinigkeiten unterscheiden. Die Aufteilung der Funktionen in dieser Art und Weise war ein iterativer Prozess über jetzt mehrere Jahre, und das ist die einzige Version, die alles kann, was wir brauchen.
-
Was spricht dagegen?
Warum ueberhaupt eine Klasse?
-
das Ding hat Parameter, eine konfigurierbare Struktur und ist alles in allem recht kompex.
Es geht darum:
AbstractModel
Das ist noch eine "alte" doku. Der Code an dem ich arbeite hat noch keine öffentlich zugängliche Dokumentation. Das ist also die Version ohne externen State und ich muss eval() und die (ersten) Ableitungen unter einen Hut kriegen.Es wird alles komplizierter dadurch, dass ich durch die Form der Ableitungen in der Lage bin, die Modelle zu verketten und dann zum Beispiel Parameterableitungen für das gesamte Modell bilden kann.
-
otze schrieb:
@cookie Es gibt verschiedene Versionen von myFunc, die sich da in Kleinigkeiten unterscheiden. Die Aufteilung der Funktionen in dieser Art und Weise war ein iterativer Prozess über jetzt mehrere Jahre, und das ist die einzige Version, die alles kann, was wir brauchen.
Gibt es nun mehrere Versionen, oder nur eine? Und ist das so schwer zu ändern? Sieht für mich nach copy pasta aus. Wenn das nicht geht, sollte man das eventuell näher beschreiben, sonst macht es ja wenig Sinn Vorschläge zu machen, bei denen man garnicht weiß was geändert werden kann und was nicht.
-
cooky451 schrieb:
Gibt es nun mehrere Versionen, oder nur eine?
Und ist das so schwer zu ändern? Sieht für mich nach copy pasta aus. Wenn das nicht geht, sollte man das eventuell näher beschreiben, sonst macht es ja wenig Sinn Vorschläge zu machen, bei denen man garnicht weiß was geändert werden kann und was nicht.wir haben bislang 4 verschiedene Versionen von myfunc, sowie 10 Modelle. Tendenz steigend. Wir müssen auch für die Zukunft etwas Luft nach oben haben, da sich die Bibliothek in einem Forschungsbereich befindet und rapid-prototyping möglich sein soll(also mal eben fix eine Idee ausprobieren und schauen, ob das funktioniert). Das Interface muss also flexibel sein und ich glaube, dass wir bereits jetzt die Flexibilität voll ausnutzen.
Deswegen glaube ich nicht, dass wesentliche Aspekte des Interfaces geändert werden können. Weitere Parameter oder neue Funktionen hinzuzufügen ist okay, da sich die Änderungen nicht weit fortpflanzen. Aber eine Änderung des grundlegenden Klassendesigns wird wohl eher nicht drin sein :(.
Oh und ich sitze alleine an der Implementation dieser Änderung ;).
//edit Es ist auch ein semantisches Problem. was myFunc im echten problem macht, ist eine Fehlerfunktion darstellen, das heißt messen wie gut das Modell A die gegebene Zielsetzung (zum Beispiel dargestellt durch
erfüllt und einen Gradienten zu berechnen, der angibt, wie das Modell geändert werden muss, um das gegebene Ziel zu erreichen. Das Modell selbst hat keine Ahnung von Zielen, es transformiert eine Eingabe nur in eine Ausgabe. Und die Ziele selbst sind zwischen den verschiedenen Lernparadigmen völlig unterschiedlich(wenn ich Regression machen will, kann ich das völlig anders aufziehen, als wenn ich clustern muss).Wenn wir auf eine schnelle Implementation verzichten wollten, würden wir den internen Zustand gar nicht brauchten (das ist ein reines Implementationsdetail für 99% aller Modelle und die restlichen 1% kann man eh nicht parallel berechnen). Und dann wäre das Design so schon ziemlich gut. Das Problem entsteht erst dadurch, dass wir den internen Zustand einführen, weil wir gerne auf single core Faktor 2 schneller wären. Und weil wir das nun auch noch parallelisieren wollen, muss der Zustand wieder aus dem Objekt raus.
//edit 2 angenommen, ich würde erstmal nur vom std::vector<double> los kommen wollen um das Interface zwar nicht unbedingt besser zu machen, aber einfacher zu verwenden. würde ich dann besser fahren, wenn ich einfach eine Basisklasse für States verwende und dann mit einem boost::polymorphic_cast oder ähnlichem in den verwendenden Klassen einen downcast mache?
oder vielleicht etwas aufgebohrt als sowas:
//skizze class State{ private: polymorphic_state_wrapper* wrapper; public: State(); template<class ModelState,class Args> void createState(Args args){ wrapper = new MyStateWrapper<ModelState>(args); } bool hasState(){ return wrapper != 0; } template<class ModelState> ModelState& getState(){ return boost::polymorphic_downcast<MyStateWrapper<ModelState>* >(wrapper)->state; } };
-
Könntest du nicht eine Art "Super-State" einführen, der Platz für alles hat? Damit hättest du den Kram nur noch einmal. Ist zwar Speicherplatzverschwendung, aber wenn das akzeptabel ist, sollte es eigentlich ziemlich simpel sein für den Rest des Codes.
-
wie meinst du das genau mit dem Super-State? Meinst du so eine Art globalen Zustand bei dem sich jedes Objekt registriert und dann rein schreibt?
also sowas wie: "ich bin objekt x in thread y gib mir mal bitte meinen State"?
also in code ausgedrückt:
struct MyModel{ private: class State{...}; unsigned int m_objectID; public: MyModel(){ m_objectID=GlobalState::registerObject();//schreit nach RAII } ~MyModel(){ GlobalState::unregister(m_objectID); } virtual Result foo(some_arg){ State& state = GlobalState::getState<State>(m_objectID);//automatischer state für jeden thread state = ...; //was auch immer sonst berechnet werden muss } virtual Result bar(transformedFoo)const{ State const& state = GlobalState::getState<State>(m_objectID); //nutze state } };sieht eigentlich auf den ersten Blick elegant aus. Eventuell macht man da am besten noch eine ordentliche Portion RAII drum und dann erhält man:
struct MyModel{ private: class State{...}; GlobalState<State> m_state; public: virtual Result foo(some_arg){ State& state = m_state.state(); state = ...; //was auch immer sonst berechnet werden muss } virtual Result bar(transformedFoo)const{ State const& state = m_state.state(); //nutze state } };voll cool eigentlich!
//edit man muss nur irgendwie garantieren, dass die thread-ids identisch bleiben :/. sollte kein Problem sein, solange das Threading von außen kommt. wenn man aber erst in foo() oder bar() threaden würde, wäre das eventuell finster...//edit X bwaah jetzt ist aber nicht mehr kodiert, dass bar nur nach foo aufgerufen werden kann. arrghl
-
Das meinte ich eigentlich nicht.^^ Das Problem war doch, dass der Typ von "State" abhängig ist von A. Du hast dann den Zustand rausgezogen und vector<double> als "generische" Schnittstelle genommen. Meine Idee war jetzt, einfach einen State zu machen, der für alle A funktioniert. Dann hat man vielleicht ein paar Stellen drin, die nur manchmal gebraucht werden, aber man muss da nichts mehr
später aus dem Ding wieder raus popeln.

-
otze schrieb:
jetzt ist aber nicht mehr kodiert, dass bar nur nach foo aufgerufen werden kann. arrghl
Mach doch so etwas:
Result myFunc(A& a, B& b,Arg& some_arg){ FooTransformed<Result> fromFoo = a.foo(some_arg); FooTransformed<Result> temp = b.transform(temp); Result res = a.bar(temp); return res; }FooTransformed<Result> ist eine Klasse die nur aus Result besteht. Die einzige Eigenschaft, die sie hat, ist, dass sie nur von A erzeugt werden kann:
struct FooTransformed<Result> { Result* operator->() { return &r; } friend class A; private: Result r; FooTransformed(Result r) : r(r) {} }; struct A{ virtual std::size_t stateSize(some_arg); FooTransformed<Result> foo(some_arg, State& state) { return FooTransformed(foo_impl(arg, state)); } virtual Result bar(FooTransformed<Result>, State const& state)=0; protected: virtual Result foo_impl(some_arg, State& state)=0; };
-
cooky451 schrieb:
Das meinte ich eigentlich nicht.^^ Das Problem war doch, dass der Typ von "State" abhängig ist von A. Du hast dann den Zustand rausgezogen und vector<double> als "generische" Schnittstelle genommen. Meine Idee war jetzt, einfach einen State zu machen, der für alle A funktioniert. Dann hat man vielleicht ein paar Stellen drin, die nur manchmal gebraucht werden, aber man muss da nichts mehr
später aus dem Ding wieder raus popeln.

Das ist bei der Variabilität schon schwierig (wir haben im worst case eine dynamische Anzahl von matrizen, vektoren, skalaren und anderem Kleinkram...). Wäre sicherlich besser als die jetzige Version...andererseits ist alles besser als die jetzige Version. Und der Ansatz führt noch immer nicht zu Code, bei dem die Zustandsvariablen ordentliche Namen haben.
hmm...
@footrans ja, an sowas habe ich auch schon gedacht. Es gibt an der Stelle aber eine einfachere Lösung, die man eventuell verwenden könnte:
Result myFunc(A& a, B& b,Arg& some_arg){ Result fooResult = a.foo(some_arg); Result temp = b.transform(temp); Result res = a.bar(fooResult,temp); return res; }da bar die Ableitung der Funktion foo ist und wir häufig den Fall haben, dass wir die Ableitung in der form f'(x)=g(f(x)) ausdrücken können, macht es sinn, an der Stelle den Funktionswert zu erwarten.
Muss ich aber mit meinen Leuten abklären, was die davon halten.
OT:
Ich bin noch immer unentschlossen. Der globale State, wie ich ihn vorgeschlagen habe ist zwar nett, insbesondere weil er die Implementationsdetails nicht nach außen trägt, andererseits sehe ich schon jetzt, dass das nur eine leaky-abstraction ist und ich in manchen Fällen trotz des versteckten Zustandes dafür Sorge tragen muss, dass ich nicht aus versehen wichtige Dinge überschreibe.
Genau so ginge ja auch:
struct MyModel{ private: class State{...}; public: virtual Result foo(some_arg, AbstractState& state)const{ State& state = state.get<State>(); state = ...; //was auch immer sonst berechnet werden muss } virtual Result bar(transformedFoo, AbstractState const& state)const{ State& state = state.get<State>(); //nutze state } };Arrgh Entscheidungen...