Come hai potuto parallelizzare una simulazione di boids 2D


16

Come è possibile programmare una simulazione di boids 2D in modo tale da utilizzare la potenza di elaborazione da fonti diverse (cluster, gpu).

esempio di boids

Nell'esempio sopra, le particelle non colorate si muovono finché non si raggruppano (giallo) e smettono di muoversi.

Il problema è che tutte le entità potrebbero potenzialmente interagire tra loro anche se è improbabile che un'entità in alto a sinistra interagisca con una in basso a destra. Se il dominio fosse diviso in diversi segmenti, potrebbe accelerare il tutto, ma se un'entità volesse attraversare un altro segmento potrebbero esserci problemi.

Al momento questa simulazione funziona con 5000 entità con un buon frame rate, vorrei provarlo con milioni se possibile.

Sarebbe possibile utilizzare i quad tree per ottimizzare ulteriormente questo? Altri suggerimenti?


Stai chiedendo l'ottimizzazione o come parallelizzare? Queste sono cose diverse.
bummzack,

@bummzack Come parallelizzare, ho appena aggiunto ulteriori spiegazioni, aiuta?
Sycren,

Risposte:


7

La tesi di laurea Parallel Simulation of Particle Fluids di Mattias Linde potrebbe offrire alcune informazioni sul partizionamento dei dati e sugli algoritmi per la simulazione su larga scala.

Il suo documento è orientato all'idrodinamica delle particelle levigate , che per la soluzione ingenua tende a utilizzare l'hashing spaziale con una dimensione del secchio attorno alla dimensione dell'impronta del kernel delle particelle nella simulazione.

Dato che la distanza di interazione è fortemente limitata nei kernel SPH tipici, tali ottimizzazioni di partizionamento sono quasi essenziali per aumentare il sistema.


bel documento, ma la parte relativa a questa domanda sembra essere molto simile alla risposta di @Fxlll.
Ali1S232,

Direi che la parte reale del documento è come risolve i casi limite introducendo un protocollo di comunicazione, questa è la parte difficile, il partizionamento quad è abbastanza ovvio e da solo non risolve il problema dei casi limite.
Maik Semder,

4

Il termine che ho imparato molto tempo fa era la velocità di informazione di un gioco.

Se la velocità delle tue boid è 1 e si preoccupano solo dei loro vicini, allora la velocità delle informazioni è 3, cioè una boid che si trova a due quadrati di distanza da te potrebbe essere all'interno del range che ti interessa all'interno di un frame:

1 movimento quadrato per boid nell'interazione (1 + 1) più la distanza che puoi notare (1) è uguale a 3.

Detto questo, apprendiamo che possiamo tagliare una mappa in pezzi, delle dimensioni che vogliamo, ma con questa velocità di informazione si sovrappongono a tutti i blocchi vicini.

Suppongo che stai permettendo alle tue boid di spostarsi di un solo quadrato, ma possono vederne tre

Se vuoi eseguire un enorme sim parallelo, ti ritroverai in griglie 10x10, ma si sovrapporranno di 5 quadrati su ciascun bordo. Ogni volta che uno dei tuoi utenti si trova alla distanza delle informazioni dal bordo del blocco locale, dovresti aggiornare il vicino e, una volta attraversato il confine, non ti appartengono. Se un vicino di casa dice che un boid che stanno controllando si è spostato nel tuo pezzo, devi assumere la sua IA.

Ciò significa che la comunicazione è localizzata ai gestori di blocchi vicini e che il traffico è ridotto al minimo. Più lavori si eseguono, più CPU è possibile utilizzare per alimentare la simulazione, ma più lavori si eseguono, più si sovrappongono e quindi maggiori informazioni passano tra lavori / blocchi man mano che la simulazione avanza. Qui è dove devi essere pesante e ottimizzare le dimensioni del blocco in base alla complessità dell'intelligenza artificiale e all'hardware disponibile.


immagina che il mondo abbia una griglia di 1.000.000x1.000.000 e ci sono 10.000.000 di boids nel mondo e ogni boid può muoversi esattamente di un quadrato ogni turno, puoi spiegare come verificare se c'è un boid nel quartiere di un altro?
Ali1S232,

Immagino che potremmo dividerlo in 2000 quadrati 500x500 o superiori. ogni quadrato contiene un elenco di boid e un elenco di vicini. Se un boid esce da un quadrato, viene rimosso dall'elenco di boid e aggiunto all'altro quadrato. Il problema con questo metodo che posso vedere è se aggiungi qualcosa con il floccaggio che è più grande del quadrato. la soluzione quadtree dovrebbe essere dinamica, ma non sono sicuro di quanto sarebbe costoso
Sycren,

@Gajet: devi solo verificare la presenza di boids nel tuo blocco o nei bordi gestiti vicini. Ricorda, il confine è garantito dalla progettazione per tenere conto di quanto qualsiasi entità può spostarsi più la distanza che le entità possono vedere. @Sycren: il floccaggio, anche se ci sembra una grande entità, è ancora solo un effetto su piccola scala. Una scuola di pesci non segue la scuola, seguono i loro vicini osservabili.
Richard Fabian,

2

Leggendo il tuo quesiton sembra che tu possa trarre vantaggio da alberi quad, creare un albero quad ed eseguire la simulazione per ogni segmento su un'unità di elaborazione diversa. Ciò farà sì che il controllo avvenga solo su oggetti vicini l'uno all'altro. ma dovrai sincronizzare i thread ogni ciclo. Ciò significa trasferire alcune di queste boid da un gruppo di elaborazione a un altro. in generale ogni ciclo sarà composto da 3 passaggi:

  1. Sposta tutte le boid di una unità. (che può essere facilmente elaborato utilizzando più thread)
  2. Assegnare ogni boid a un gruppo *. Questo significa che usando un algoritmo di O (n) devi selezionare quali boids hanno più probabilità di fare una collisione. Questo può anche essere gestito usando più thread.
  3. Alla fine devi controllare se due boids in uno stesso gruppo hanno fatto una collisione.

* Per creare gruppi è possibile utilizzare il modello seguente:

inserisci qui la descrizione dell'immagine

si noti che alcune boids possono far parte di più di un gruppo, ma questo modello fornisce risultati più accurati. puoi anche creare tutti i gruppi che vuoi usando questo modello, è solo un numero che devi trovare per quante boid e lo schermo quali dimensioni dello schermo, qual è il miglior numero di gruppi che devi creare.

--modificare--

c'è un'altra idea sulla segmentazione descritta nel documento @LarsViklund suggerito, in questo modo ci sono molti meno controlli doppi e non è necessario aumentare / ridurre il numero di thread tra i passaggi:

inserisci qui la descrizione dell'immagine

si noti che alcune aree fanno ancora parte di due gruppi. e la larghezza dell'area sia la copertura del gruppo è esattamente 2*maximum speed. Nel tuo caso, se le boids si muovono di un pixel per passaggio di simulazione, devi solo condividere l'area della larghezza di 2 pixel tra ogni 2 gruppi. e c'è una piccola area che fa parte di 4 gruppi. ma in generale questo metodo è più facile da implementare e molto più veloce se implementato correttamente. e tra l'altro non vi è alcun movimento inverso in questo modo, se un oggetto può spostarsi, non può spostarsi più non è richiesto alcun controllo.


Sembra una buona idea, ma prima di passare al punto 1, avrei bisogno di fare il rilevamento delle collisioni per vedere se possono muoversi, no?
Sycren,

È possibile spostarli, quindi verificare se si verifica una collisione inversa che si sposta (per quel boid esatto), altrimenti lasciare che la simulazione continui.
Ali1S232,

Grazie, ha più senso. Oltre ai quadrifici, puoi pensare a un altro modo per dividere il carico di lavoro?
Sycren,

Come puoi vedere, le mie segmentazioni non sono completamente un albero quad, ma hanno un altro gruppo in più per aumentare la precisione, lo stile dell'albero dei quad è solo molto più facile da gestire. A seconda delle dimensioni del mondo è possibile aggiungere più gruppi, il che significa meno controlli in ogni ciclo. è un compromesso tra consumo di memoria e velocità di elaborazione. e non deve necessariamente essere un thread per ciascun gruppo. puoi avere alcuni thread per calcolare più di un gruppo. Puoi anche dividere i calcoli di un gruppo tra due o più thread.
Ali1S232,

@Gajet se capisco bene la tua foto, ci sarebbero molti doppi calcoli, poiché le aree sovrapposte dei gruppi sono molto grandi. Dato che la domanda chiede di simulare fino a qualche milione di punti, sarebbe uno spreco enorme.
Maik Semder

2

Di recente ho affrontato questo problema utilizzando alcune di queste risposte come punto di partenza. La cosa più utile da tenere a mente è che le boids sono una sorta di semplice simulazione n-body: ogni boid è una particella che esercita una forza sui suoi vicini.

Ho trovato difficile leggere il documento di Linde; Suggerisco invece di guardare "Fast Parallel Algorithms for Short-range Molecular Dynamics" di SJ Plimpton , a cui Linde faceva riferimento. Il documento di Plimpton è molto più leggibile e dettagliato con cifre migliori:

In breve, i metodi di decomposizione atomica assegnano permanentemente un sottoinsieme di atomi a ciascun processore, i metodi di decomposizione forzata assegnano un sottoinsieme di calcoli di forza a coppie a ciascun proc e i metodi di decomposizione spaziale assegnano una sottoregione della scatola di simulazione a ciascun proc .

Ti consiglio di provare AD. È il più facile da capire e implementare. FD è molto simile. Ecco la simulazione n-body di nVidia con CUDA utilizzando FD, che dovrebbe darti un'idea approssimativa di come la piastrellatura e la riduzione possono aiutare a superare drasticamente le prestazioni seriali.

Le implementazioni di SD sono generalmente tecniche di ottimizzazione e richiedono un certo grado di coreografia per essere implementate. Sono quasi sempre più veloci e si adattano meglio.

Questo perché AD / FD richiede la creazione di un "elenco vicino" per ogni boid. Se ogni boid deve conoscere la posizione dei suoi vicini, la comunicazione tra loro è O ( n ²). È possibile utilizzare gli elenchi dei vicini Verlet per ridurre la dimensione dell'area verificata da ogni boid, il che consente di ricostruire l'elenco ogni pochi timestep anziché ogni passaggio, ma è comunque O ( n ²). In SD, ogni cella mantiene un elenco vicino, mentre in AD / FD ogni boid ha un elenco vicino. Quindi, invece di ogni boid che comunica tra loro, ogni cellula comunica tra loro. Quella riduzione della comunicazione è da dove viene l'aumento della velocità.

Sfortunatamente il problema delle boids sabota leggermente SD. Fare in modo che ciascun processore tenga traccia di una cella è più vantaggioso quando le boid sono distribuite in qualche modo uniforme nell'intera regione. Ma vuoi che i boids si raggruppino insieme! Se il tuo gregge si comporta correttamente, la stragrande maggioranza dei tuoi processori andrà via, scambiando liste vuote tra loro e un piccolo gruppo di celle finirà per eseguire gli stessi calcoli che AD o FD farebbero.

Per far fronte a questo, puoi sia matematicamente ottimizzare la dimensione delle celle (che è costante) per minimizzare il numero di celle vuote in un dato momento, oppure utilizzare l'algoritmo Barnes-Hut per i quad-alberi. L'algoritmo BH è incredibilmente potente. Paradossalmente, è estremamente difficile implementare su architetture parallele. Questo perché un albero BH è irregolare, quindi i fili paralleli lo attraverseranno a velocità selvaggiamente variabili, con conseguente divergenza del filo. Salmon e Dubinski hanno presentato algoritmi di bisection ricorsivi ortogonali per distribuire equamente i quadrifogli tra i processori, che devono essere ripetuti ripetutamente per la maggior parte delle architetture parallele.

Come puoi vedere, siamo chiaramente nel regno dell'ottimizzazione e della magia nera a questo punto. Ancora una volta, prova a leggere il documento di Plimpton e vedi se ha senso.


1

Suppongo che il tuo sia un sistema toroidale, è possibile partizionare nello spazio in modo che ogni unità abbia la sua area secondaria.

Ad ogni passo le particelle vengono spostate, le particelle che escono dall'area secondaria vengono inviate al relativo processore; un passo di comunicazione sincronizzerà i processori e verrà fatto un ultimo post per elaborare la posizione delle particelle estranee (se presente).

Qui ci sono tre problemi qui:

  • 1) la forma della sottoarea:

Si può optare per i rettangoli ma mostrare un piccolo rapporto Area / perimetro rispetto ai circhi. Più grande è il bordo, più particelle lasceranno. Mentre i cicles mostrano il miglior rapporto A / p, non possono essere usati per la tassellatura, quindi dovresti indagare per alcune tassellature (possibilmente semi regolari) con un buon rapporto A / p medio. Ovviamente calcolare l'indice di nappa per coordinata cellulare dovrebbe essere semplice, quindi consideralo prima di provare una tassellazione molto esotica.

  • 2) il protocollo di comunicazione:

A seconda del tipo di infrastruttura di comunicazione che possiedi, puoi pensare a come spargere le informazioni sui valichi di frontiera tra i processori. Trasmissione vs ricostruzione peer-to-peer vs comunicazione peer-to-peer sono tutte opzioni.

  • 3) la ripartizione per sotto-area:

Dovresti mantenere la tua elaborazione equilibrata poiché c'è una sincronizzazione per ogni passaggio. È possibile scegliere di allocare staticamente o dinamicamente le aree ai processori. Questo non è un grosso problema se il tuo spazio è uniformemente coperto da particelle attive, ma credo che in questo caso possa essere falso poiché le collisioni disattivano le particelle. La modifica dell'allocazione richiede un passaggio di comunicazione più pesante; alcune scorciatoie possono essere prese se tutti i processori condividono le informazioni transfrontaliere ma bisogna tenerne conto


@Fxlll Non sono sicuro di cosa intendi per sistema toroidale, non ha la forma di una ciambella. Vuoi dire che se una particella si stacca dal lato destro, riappare a sinistra? In tal caso, non è così, se una particella colpisce il lato destro prova a muoversi in una direzione diversa.
Sycren,

@Sycren ok in questo caso devi fare qualche considerazione sulla tassellazione e sul trattamento dell'area sul bordo in modo speciale
FxIII

-1

Prova la mia simulazione per indizi https://github.com/wahabjawed/Boids-Simulation

L'ho sviluppato su XNA


Il semplice collegamento a un progetto completo non è una buona risposta. Il lettore è costretto a scavare attraverso la tua fonte fino a quando non trova la parte che è rilevante per la domanda e quindi deve ancora capire come risolve il problema. Puoi descrivere in parole povere come hai affrontato il problema e quali vantaggi ha rispetto alle soluzioni descritte nelle altre risposte? Puoi copiare e incollare alcuni frammenti di codice funzione nella tua risposta se aiutano a comprendere la tua descrizione.
Philipp,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.