C++ Datenhandhabung für möglichst perfomante Simulation?

T_55

Lieutenant
Registriert
Feb. 2013
Beiträge
643
Hallo,

anhand von historischen Klimadaten will ich eine Simulation für eine Sonnenstudie machen. Funktionen durchlaufen also die historischen Datenpunkte die im Prinzip eine Tabelle mit vielen Spalten sind. Ich würde die Daten in SQLite oder vielleicht auch einfach als CSV vorbereiten.
Wenn die Funktion die Datenpunkte durchschleift und bei jeder Zeile, wo zu dem Zeitpunkt eine Reihe Messwerte liegt, neu auf die Datenbank zugreifen muss, wird das vermutlich die Performance beeinträchtigen da der Flaschenhals die Festplatte ist, so denk ich mal. Die Daten können durchaus mehrere GB groß sein, so dass ein vollständiges Vorladen des kompletten Datensatzes in ein Array also in den RAM zu viel wäre.

Da ich verschiedene Funktionen mit verschiedenen Einstellungen durchtesten will würde ich gerne den Prozessor voll ausnutzen können und mehrere Simulationen in Threads stecken. Das würde dazu führen, dass die Datenquelle von mehreren Threads genutzt wird und damit würde die CSV eigentlich rausfallen. SQLite scheint das gleichzeitige Lesen zu unterstüzen. Man könnte vielleicht intervallweise die Daten auslesen also zum Beispiel immer nur 500MB aus der Datenbank in ein Array im Thread legen und dann Simulieren und dann neue 500MB laden usw. So würde man den RAM nicht überladen und die 500MB würden schnell an einem Stück aus dem RAM verarbeitet werden.
SQLite unterstützt wohl auch In-Memory vielleicht kann man damit auch was machen... kenne mich aber nicht aus damit.

Ziel wäre einfach ein Ansatz der trotz großer Daten die allergröße Performance rausholt und dabei aber den Threads auch erlaubt auf die gleichen Daten zuzugreifen. Bevor ich hier anfange Unsinn zu coden würde mich mal interessieren wie Ihr das angehen würdet. Gibt es Ansätze die besonders performant sind für sowas?

Gruße
 
Was für eine Simulation möchtest du denn implementieren? Wie sieht der algorithmus aus? Hast du vielleicht schon eine Beispielimplementierung für die Simulation eines kleinen Datensatzes?

edit: mapreduce geht nur, wenn man im algorithmus nicht sowas wie loop carried dependencies hat, oder?
 
Zuletzt bearbeitet:
Sofern es sich nicht um ein Kleinprojekt handelt, wuerde ich schauen entsprechend grossen RAM anzuschaffen und die Daten dort einmalig ablegen - und zwar in einem Format sodass beim anschliessenden Lesen cache-misses moeglichst vermieden werden. Langfristig ist das x-fach effizienter als partielles Datenladen.

Ansonsten 2 Gruppen an threads: Die erste Gruppe ist fuer Datenbeschaffung zustaendig, die zweite fuer die Simulationen auf vorhandene Daten. Gut moeglich dass fuer die erste Gruppe der main-thread alleine reicht.
Schematisch so:
1) main-thread holt erstes Paket an Daten; die n threads fuer die Simulationen warten auf die Lieferung dieser Daten
2) wenn Daten da: Simulationen bearbeiten die vorhandenen Daten, gleichzeitig wird das naechste Paket an Daten geholt.
3) wenn alle Simulationen ein Datenpaket bearbeitet haben, informieren sie den main-thread dass der Speicher freigegeben / fuers naechste Paket recycled werden kann
Schritte 2-3) wiederholen, wobei max. m Datenpakete vorgeladen werden. m = 2 heisst also dass die Simulationen und das Laden des naechsten Paketes gleichzeitig stattfinden, genauso aber auch nicht mehr als 2x500 MB Ram fuer Daten benoetigt werden werden

Wichtig ist die korrekte thread-Synchronisation: die Simulationen duerfen erst beginnen ein Datenpaket zu bearbeiten wenn es komplett geladen ist; und ein Datenpaket darf erst wieder freigegeben werden wenn alle Simulations-threads mit seiner Bearbeitung fertig sind. Also alles brav mit mutexes & condition variables synchronisieren.
 
Nicht genau deine Fragestellung, aber du solltest beim Data Crunching auch den Aspekt der Cache-Freundlichkeit im Auge behalten. Das bedeutet, dass Daten, auf denen Berechnungen durchgeführt werden, am besten dicht beieinander im Speicher liegen. Das erhöht die Chance, dass die als nächstes benötigten Daten bereits in die CPU-Caches vorgeladen wurden, was die Berechnungsgeschwindigkeit erheblich erhöht.

Statt eines Arrays von Structs mit Feldern für die einzelnen Spalten, ist es meistens effizienter, ein Struct mit Arrays (Spaltenvektoren) zu verwenden. Beim Iterieren über die Daten sollten viele kleine Schleifen hintereinander bevorzugt werden, statt einer großen Schleife, die mehrere Spalten parallel berechnet. Beides bewirkt, dass die Caches besser ausgenutzt werden durch die dichte Packung der Daten.
 
Danke für die Tipps. Ich denke der Ansatz einmalig die Daten auf den RAM zu packen ist natürlich am pragmatischsten. Mit meinen 8GB würde ich bei zB 3GB Daten zwei Threads starten können. Wenn ich auf 16GB aufrüste wären es schon knapp fünft Threads. Oder 24GB dann etwa 7 Threads. Bei einem 8-Kern-CPU macht es wahrscheinlich sowieso kein Sinn mehr als 8 Threads zu starten.

Mit der Aufteilung in Datenbeschaffungsthread und Simulationstreads (sehr coole Idee!) würde man dem RAM-Limit aus dem Weg gehen denn es könnte schon sein das ich vielleicht mal eine 6GB Datei durchschleifen muss dann wird es auch mit viel RAM eng. Flexibler wäre es auf jeden Fall. Der Datenbeschaffungsthread muss also ein Teilpaket aus der Datenbank in sich hineinkopieren. Und jeder Thread muss dann dieses Paket wiederum in sich hineinkopieren. Die Threads müssen dann auf sich warten ausser jeder Thread hat ein eigenen Datenbeschaffungsthread was aber den RAM wieder mehr auslasten würde weil dann die Daten nicht nur in den Threads parallel vorliegen können sondern auch in den Beschaffungsthreads. Vorteil wäre aber die Threads müssen nicht auf die anderen warten, dafür müsste man die Pakete aber sicher kleiner machen.

Das mit dem Cache ist auch spannend. Struct mit Arrays die einzelnd iteriert werden, das klingt doch gut. Wären dann klassische Arrays dem std::vector vorzuziehen?
Ergänzung ()

Ein Array scheint wohl schneller zu sein als ein Vector. Die Größe ist sowieso fix (je nach Paketgröße) dann einfach im Thread das Array entsprechend initialisieren sobald der Thread startet, Daten draufpacken und nutzen.
 
Je nach Datenbanksystem ist es wahrscheinlich die beste Option die Berechnungen direkt dem Datenbanksystem zu übergeben. Wobei du da genauso Konzepte nutzen kannst wie sie hier genannt wurden. Das Aufteilen großer Jobs auf kleinere Teile ist sehr oft sinnvoll. Zum einen weil sich diese Teilprobleme parallel bearbeiten lassen und wenn irgendwas schief geht verliert man nur die letzten n Teile (n.. Anzahl der laufenden Vorgänge).
Wenn du deine Daten in Strukturen packst und je nach Algo dann noch mit einem Suchbaum versiehst, damit der Spaß skaliert, dann baust du mehr oder weniger eine Datenbank nach. Nur wahrscheinlich deutlich schlechter, Dann nimm lieber gleich ein gescheites DBS mit guten Funktionalitäten in Mathe / Statistik bzw. guten Plugins dafür. Zudem musst du dich wenn du ein DBS nutzt kaum mehr um die Speicherverwaltung kümmern.


Wenn du das aus Spaß an der Freude das trotzdem in C++ abbilden willst:

*Ich würde schauen, dass anstatt die Daten laufend aus der Datenbank zu hohlen und in eine Struktur zu überführen wie sie dein Programm erwartet diese möglichst in einem Schwung mit so vielen Threads wie sinnvoll in eine Form zu überführen wie sie dein Programm erwartet und in binärer Form zu speichern. Danach kannst du die Datenbank herunterfahren. Damit parst du die Daten nur einmal (egal ob aus dem DBS kommend oder von einer .csv) anstatt da immer wieder diesen Overhead zu haben. Gerade wenn wie in deinem Fall ein Datenbanksystem im Hintergrund auch noch Arbeitsspeicher braucht und das nur um Daten zu liefern anstatt sie auch zu verarbeiten ist eher mau.

*Das ganze Multithreaded abzuwickeln sollte klar sein, je nach Algorithmus musst du dich da aber mit gegenseitigem Blockieren beschäftigen, sie Philosophenproblem

*Die Performance von C / C++ lassen sich über Compilereinstellungen mitunter extrem beschleunigen. Genauso gibt es Bibliotheken, die div. mathematische Operationen für div. CPUs Architekturen optimieren und sich in dein Programm einbinden lassen. Ebenso wie du mit Späßen wie OpenMP / Cilk Plus und Ähnlichem ein sehr weites Feld an Technologien hast mit denen du Performance schinden kannst.
Bevor du dieses Thema aber komplett durchstiegen hast, wirst du Zeit im Bereich von vielen Monaten / Jahren aufwenden müssen.
 
T_55 schrieb:
Bei einem 8-Kern-CPU macht es wahrscheinlich sowieso kein Sinn mehr als 8 Threads zu starten.

Kommt auf den task an; solange nicht alle threads exakt den gleichen workload haben, macht es schon Sinn etwas mehr threads zu starten (z.B. 12).

T_55 schrieb:
Der Datenbeschaffungsthread muss also ein Teilpaket aus der Datenbank in sich hineinkopieren. Und jeder Thread muss dann dieses Paket wiederum in sich hineinkopieren. Die Threads müssen dann auf sich warten ausser jeder Thread hat ein eigenen Datenbeschaffungsthread was aber den RAM wieder mehr auslasten würde weil dann die Daten nicht nur in den Threads parallel vorliegen können sondern auch in den Beschaffungsthreads. Vorteil wäre aber die Threads müssen nicht auf die anderen warten, dafür müsste man die Pakete aber sicher kleiner machen.
Das kommt auch darauf an WIE die Daten geholt werden sollen. Wenn die Quelle der bottleneck ist und multiple Zugriffe mehr overhead verursachen, macht es die Sache nur langsamer.
Ein effizientes Schema zum Daten-holen kann so ausschauen: EINMALIG (z.B. am Beginn deines Programms, oder ueberhaupt per separatem Programm) liest du deinen ganzen Datensatz ein, speicherst ihn in deiner Zielstruktur ab, und schreibst die dann im Binaerformat raus, und zwar organisiert in diskreten chunks mit einfachem Zugriff auf jeden chunk (z.B. jeder chunk in einer separaten Datei, oder an bekannter Stelle in einer einzigen grossen Datei). Und beim Lesen liest du dann direkt diese Binaerdaten zurueck - was so schnell gehen sollte wie dein Datentraeger ist (und z.B. um orders-of-magnitude schneller ist als eine csv-Textdatei zu lesen).

T_55 schrieb:
Das mit dem Cache ist auch spannend. Struct mit Arrays die einzelnd iteriert werden, das klingt doch gut. Wären dann klassische Arrays dem std::vector vorzuziehen?

Meinst du arrays im Sinne von statischer Alloziierung am stack? Der Zugriff darauf sollte schneller sein, aber da bist du sehr beschraenkt bzgl. deren max. Groesse. 500 MB, oder selbst nur ein Bruchteil davon, kannst da komplett vergessen.
Wichtig ist dass deine Daten so angeordnet sind, dass sequentielle Zugriffe darauf v.a. benachbarte Speicherregionen betreffen. Du hast ja quasi eine Tabelle mit n Zeilen und m Spalten. Wenn du nun z.b. tendenziell auf die Werte zeilenweise zugreifst (also zuerst alle Spalten der ersten Zeile, dann alle Spalten der zweiten Zeile etc.) sollten die Daten auch zeilenweise angeordnet sein (z.b. ein vector der Laenge n * m, wo die ersten m Werte der ersten Datenzeile entsprechen, etc.). Bei spaltenweisem Zugriff genau umgekehrt.


Mal was anderes: Findet auf deine Daten eigentlich auch ein direkter Schreibzugriff statt (z.B. weil output deiner Simulationen direkt da hineingeht)? Denn falls nicht, also wenn die Daten nur gelesen werden, dann hast dus sowieso viiiel einfacher: Du liest die Daten einmalig in dein Programm ein (ist ja dann Banane ob das schneller oder etwas langsamer geht), dein RAM reicht ja eh fuer den ganzen Datensatz, und alle threads greifen dann lesend auf diese eine Instanz der Daten zu. Und jeder thread bzw. jede Simulation schreibt halt separiert seinen output irgendwohin. Und selbst falls der output quasi dieselbe Struktur/Groesse wie die Originaldaten haben sollte, kannst du ja trotzdem eine Instanz der Originaldaten fuer alle threads verwenden und den Trick mit den Datenchunks nur fuer den output anwenden (i.e. jeder thread schreibt, wenn er 500 MB an output kreiert hat, die dann raus).
FAlls in die Originaldaten hineingeschrieben wird musst du sowieso ganz genau darauf achten, dass sich die threads nie gegenseitig lesend&schreibend in die Quere kommen.
 
Wenn du hier nichts darüber sagst, wie genau der Algorithmus arbeiten soll bzw. welcher Algorithmus implementiert wird, dann kann ich dir auch nicht sagen, welche der hier schon zahlreich genannten Optimierungstechniken wirklich etwas bringt. Das ist wirklich die wichtigste Frage, die du am Anfang eines solchen Projekts beantworten musst, wenn du strukturiert vorgehen möchtest.
Wenn du z.b. jeden Datenbankeintrag nur 1x anfasst, dann macht es keine Sinn auf Ausnutzen des Caches zu optimieren.
 
aroxx schrieb:
Wenn du hier nichts darüber sagst, wie genau der Algorithmus arbeiten soll bzw. welcher Algorithmus implementiert wird, dann kann ich dir auch nicht sagen, welche der hier schon zahlreich genannten Optimierungstechniken wirklich etwas bringt. Das ist wirklich die wichtigste Frage, die du am Anfang eines solchen Projekts beantworten musst, wenn du strukturiert vorgehen möchtest.
Wenn du z.b. jeden Datenbankeintrag nur 1x anfasst, dann macht es keine Sinn auf Ausnutzen des Caches zu optimieren.

Den oder die Algos müssen erst noch erstellt werden. Es läuft darauf hinaus, dass der Input also die Klimawerte (Temperaturen und Werte der Sonneneinstrahlung auf ein Objekt an verschiedenen Himmelsrichtungen zu den jeweiligen Zeiten über Jahre hinweg) genutzt werden um damit zu schauen wie sich beim Verstellen von Verschattungsparametern die Temperaturen des Objektes manipulieren lassen.
Einer der Algos würde zB. auf bestimmte Grenzwerte der Sonneneinstarhlung reagieren und so die Objekttemperatur fiktiv manipulieren.


firespot schrieb:
Mal was anderes: Findet auf deine Daten eigentlich auch ein direkter Schreibzugriff statt (z.B. weil output deiner Simulationen direkt da hineingeht)?

Auf die Daten findet kein Schreibzugriff statt, die Simulationen lesen nur davon. Ergebniswerte werden nur einmal nach einem Durchlauf gespeichert um dann zu vergleichen.

firespot schrieb:
dein RAM reicht ja eh fuer den ganzen Datensatz, und alle threads greifen dann lesend auf diese eine Instanz der Daten zu

Das wäre super aber geht das? Können zB 12 Threads parallel auf die selben Daten im RAM zugreifen? mutex lock und unlock würde doch dann bei 12 Threads ziemlich ausbremsen oder?

firespot schrieb:
Ein effizientes Schema zum Daten-holen kann so ausschauen: EINMALIG (z.B. am Beginn deines Programms, oder ueberhaupt per separatem Programm) liest du deinen ganzen Datensatz ein, speicherst ihn in deiner Zielstruktur ab, und schreibst die dann im Binaerformat raus, und zwar organisiert in diskreten chunks mit einfachem Zugriff auf jeden chunk (z.B. jeder chunk in einer separaten Datei, oder an bekannter Stelle in einer einzigen grossen Datei). Und beim Lesen liest du dann direkt diese Binaerdaten zurueck - was so schnell gehen sollte wie dein Datentraeger ist (und z.B. um orders-of-magnitude schneller ist als eine csv-Textdatei zu lesen).

Binear heißt das dann in einer Datenbank die Daten als BLOB zu speichern?
 
Datensätze parallel zu lesen sollte zu keinem Lock führen.

Wenn firespot etwas zu "Daten rauslassen" schreibt, dann meint er die Daten zu extrahieren, als Binärformat zu speichern und eben nicht über die Datenbank zu laufen. Datenbankzugriffe und das parsen der Antwort aus dem DBS ist mit allerhand Overhead verbunden und je nach Zugriffsmuster auf die Daten kann es daher sinnvoll sein die Daten eben nicht übers DBS zu adressieren.
 
Piktogramm schrieb:
Wenn firespot etwas zu "Daten rauslassen" schreibt, dann meint er die Daten zu extrahieren, als Binärformat zu speichern und eben nicht über die Datenbank zu laufen. Datenbankzugriffe und das parsen der Antwort aus dem DBS ist mit allerhand Overhead verbunden und je nach Zugriffsmuster auf die Daten kann es daher sinnvoll sein die Daten eben nicht übers DBS zu adressieren.

Also binär dann im RAM vorbereitet oder in einer Datei das hab ich noch nicht verstanden?

Piktogramm schrieb:
Datensätze parallel zu lesen sollte zu keinem Lock führen.

Dachte immer auch lesend muss beim Zugriff auf eine Variable gesperrt werden wenn es viele tun. Also so würde das zugriffsmäßig gehen? (mal abgesehen davon, dass im Beispiel die Threads kein Ende finden :)).

Code:
int ReadMe = 100;

void funktion()
{
   while(1)
   {
       int a = ReadMe;
   }
}

int main()
{
   std::thread t1(funktion);
   std::thread t2(funktion);
   std::thread t3(funktion);
   std::thread t4(funktion);
   std::thread t5(funktion);
   std::thread t6(funktion);
   std::thread t7(funktion);
   std::thread t8(funktion);
}
 
Noch eine Sache, die einen großen Unterschied machen kann: Branch Prediction durch die CPU Pipeline. Man kann der CPU helfen, richtig zu raten, welcher Code als nächstes ausgeführt wird. Wenn die CPU immer richtig rät, werden alle Ausführungseinheiten voll ausgelastet. Wenn nicht, müssen spekulativ im Voraus berechnete Ergebnisse verworfen werden und die Berechnungen müssen wiederholt werden.

- wenn irgendwie möglich, sollten if-Bedingungen vermieden/ersetzt werden, z.B. durch mathematische Kniffe.
- ansonsten sollten if-else-Verzweigungen möglichst geordnet ablaufen. Erst immer if, später nur noch else, oder abwechselnd if und else, etwas in der Art.
- das bezieht sich natürlich nur auf die innersten, rechenaufwendigsten Schleifen, also auf den optimierungswürdigen Code.

Hier ein berühmter und sehr lesenswerter Thread auf Stackoverflow zu dem Thema: http://stackoverflow.com/questions/...process-a-sorted-array-than-an-unsorted-array
 
T_55 schrieb:
Also binär dann im RAM vorbereitet oder in einer Datei das hab ich noch nicht verstanden?

Was ich meine: Du holst dir die Daten aus deiner Quelle (z.B. eine csv-Datei mit den Daten einlesen, oder aus einer Datenbank abrufen) und speicherst sie in einer Instanz einer selbstgebastelten Klasse ab. Natuerlich konstruierst du diese Klasse gemaess der Struktur deiner Daten, um anschliessend bequemen Zugriff auf die einzelnen Datenelement zu ermoeglichen.

T_55 schrieb:
Dachte immer auch lesend muss beim Zugriff auf eine Variable gesperrt werden wenn es viele tun. Also so würde das zugriffsmäßig gehen? (mal abgesehen davon, dass im Beispiel die Threads kein Ende finden :)).

Wenn alle threads nur lesend auf die Daten zugreifen brauchst du KEINE Synchronisation zw. den threads. Sofern ich mich nicht komplett irre garantiert dir der Standard dass "ReadMe" mit 100 initialisiert wird bevor die execution von main beginnt; und somit initialisiert auch jeder thread die Variable "a" mit 100. Solange also kein thread den Wert von "ReadMe" auch irgendwie aktiv veraendert brauchst du keine weitere Synchronisation, mutex oder sonstwas! Dabei ist es egal ob "ReadMe" ein simpler integer ist, oder eine Klasse die intern 6GB+ an Daten speichert. Wenn du also eine saubere Klasse erstellst, wo etwa alle const Memberfunktionen auch wirklich nur read-only sind, kannst du somit deinen ganzen Datensatz einmalig einlesen & speichern (in einer einzigen Instanz dieser Klasse), und anschliessend alle threads dann einfach auf die const Memberfunktionen dieser einen Klasseninstanz zugreifen lassen.
Damit sollte die Welt fuer dich doch viel einfacher sein, weil sich kein thread mehr um die Datenbeschaffung kuemmern muss - er hat eh vollen Zugriff auf den ganzen Datensatz, und auch egal wieviele threads zu startest :)
 
Wenn du genaueres weißt, wie die Berechnung läuft kann man auch anders Optimieren. Wenn du z.B die Berechnung durch Matrizen machen kannst, könntest du auch eine BLAS Library verwenden. Diese sind häufig ziemlich gut optimiert und liefern sehr gute Performance.
Soweit wie ich das verstanden habe representiert ein Datensatz (eine Zeile) eine Messzeitpunkt, dabei werden verschiedene Messwerte erhoben. Du möchtest jetzt diese Messwerte für alle Messzeitpunkte neuberechnen? Falls ja, wäre das ein Fall für SIMD Instructions - Single Instrcution Multiple Data. Wenn du C++ verwenden willst, könntest du dich (falls du möchtest) hier auch in SSE2 bzw. AVX einlesen.

Wegen RAM, wenn das in Rahmen einer wissenschaftlichen Arbeit gemacht werden soll, kannst du auch einfach in einer Cloud VM die Simulation laufen lassen. Je nachdem wie häufig die Simulation laufen wird, kann das billiger sein als RAM zu kaufen. Microsoft und Amazon bieten hier extra "Research Stipendien" an (https://www.microsoft.com/en-us/research/academic-program/microsoft-azure-for-research/).

Wenn es im Rahmen eines Uni Projektes läuft, kannst du dich auch bei Microsoft Student Partners melden, die können dir auch Azure Credits geben.
 
Du machst dir schon viel zu detaillierte Gedanken - genau wie aroxx schon sagte.
Versuch erstmal überhaupt die Daten in den Ram einzulesen und den Algo singlethreaded zum laufen zu bringen.
Selbst wenn das dann zu langsam ist, wäre es IMMERNOCH zu früh sich über Optimierungen Gedanken zu machen wie sie hier im Thread diskutiert werden denn du musst erstmal messen, was überhaupt das Problem ist.
Das ganze nennt man sonst premature optimization und bedeutende Computerwissenschaftler nennen es nicht ohne Grund "the root of all evil"
Ohne eine verlässliche, wiederholbare und automatisierte Messung kannst du überhaupt nicht feststellen, ob irgendeine Änderung überhaupt einen positiven Effekt hat und du weißt überhaupt nicht, was eigentlich zu optimieren ist.
 
Zuletzt bearbeitet:
@kuddlmuddl:

Ich stimme dir vollkommen zu dass premature optimization zu vermeiden ist. Aber Aspekte wie z.B. ob (einigermassen effizientes) multithreading ermoeglicht werden kann oder nicht ist oftmals eine ganz zentrale Frage vom software design (z.b. tasks moeglichst unabhaengig voneinander zu gestalten, Ressourcen teilen, Synchronisationsnotwendigkeiten minimieren) und sollte somit vom Beginn weg entsprechend beruecksichtigt werden. Ich rede hier also keineswegs davon den multithreading-code auch gleich zu implementieren (ich bin da ganz bei dir dass am Beginn eine funktionfaehige single-threaded Version stehen sollte); sondern ob das ganze Werk auch so ausgelegt ist, dass die multithreading Funktionalitaet spaeter auch mit relativ wenig Aufwand hinzugefuegt werden kann.

Ich denke das ist einer der Gruende, warum auch im Jahre 2017 noch immer soviel single-threaded ablaeuft: weil das ganze design schlicht und einfach eine Sackgasse fuer multithreading war/ist.
 
Kommt halt auf die Problemklasse an und dafür braucht man zunächst mal einen Algorithmus.
Den oder die Algos müssen erst noch erstellt werden.

Komm wieder, wenn das fertig ist. Einfach hier posten, als Pseudocode oder als mathematische Formulierung. Dann können wir uns überlegen, was sinnvoll ist :)
 
Zurück
Oben