Come ottimizzare la funzione di distanza?


23

Durante lo sviluppo di un gioco simile a RTS abbastanza semplice, ho notato che i miei calcoli della distanza stavano causando un impatto sulle prestazioni.

In ogni momento, ci sono controlli di distanza per sapere se un'unità si trova nel raggio d'azione del suo bersaglio, se il proiettile ha raggiunto il suo bersaglio, se il giocatore ha investito un pickup, una collisione generale, ecc. L'elenco continua e controlla la distanza tra due punti viene usata molto.

La mia domanda riguarda esattamente questo. Voglio sapere quali alternative hanno gli sviluppatori di giochi per controllare le distanze, oltre al solito approccio sqrt (x * x + y * y), che richiede abbastanza tempo se lo eseguiamo migliaia di volte per frame.

Vorrei sottolineare che sono a conoscenza delle distanze di Manhattan e dei confronti a distanza quadrata (saltando il collo di bottiglia sqrt). Qualunque altra cosa?



Se hai oggetti che non ti aspetti di spostare, come gli edifici, ad esempio, potrebbe valere la pena prendere una serie di taylor 2D della funzione di distanza, troncarla al termine del quadrato e quindi memorizzare la funzione risultante come funzione di distanza da quel particolare edificio. Ciò trasferirebbe parte del lavoro grugnito all'inizializzazione e potrebbe accelerare un po 'le cose.
Alexander Gruber,

Risposte:


26

TL; DR; Il tuo problema non è con l'esecuzione della funzione di distanza. Il tuo problema sta eseguendo la funzione di distanza così tante volte. In altre parole, hai bisogno di un'ottimizzazione algoritmica piuttosto che matematica.

[EDIT] Sto eliminando la prima sezione della mia risposta, perché le persone la odiano. Il titolo della domanda chiedeva funzioni di distanza alternative prima della modifica.

Si sta utilizzando una funzione di distanza in cui si calcola ogni volta la radice quadrata. Tuttavia, puoi semplicemente sostituirlo senza usare la radice quadrata e calcolare invece la distanza al quadrato. Questo ti farà risparmiare un sacco di cicli preziosi.

Distanza ^ 2 = x * x + y * y;

questo è in realtà un trucco comune. Ma devi modificare i tuoi calcoli di conseguenza. Può anche essere usato come controllo iniziale prima di calcolare la distanza effettiva. Ad esempio, invece di calcolare la distanza effettiva tra due punti / sfere per un test di intersezione, possiamo invece calcolare la distanza al quadrato e confrontarla con il raggio al quadrato invece del raggio.

Modifica, ben dopo che @ Byte56 ha sottolineato che non avevo letto la domanda e che eri consapevole dell'ottimizzazione della distanza al quadrato.

Bene nel tuo caso, sfortunatamente siamo nella computer grafica quasi esclusivamente allo spazio euclideo e la distanza è definita esattamente come Sqrt of Vector dot itselfnello spazio euclideo.

La distanza al quadrato è la migliore approssimazione che otterrai (in termini di prestazioni), non riesco a vedere nulla che batte 2 moltiplicazioni, un'aggiunta e un incarico.

Quindi dici che non posso ottimizzare la funzione di distanza cosa devo fare?

Il tuo problema non è con l'esecuzione della funzione di distanza. Il tuo problema sta eseguendo la funzione di distanza così tante volte. In altre parole, hai bisogno di un'ottimizzazione algoritmica piuttosto che matematica.

Il punto è, invece di controllare l'intersezione del giocatore con ogni oggetto nella scena, ogni fotogramma. Puoi facilmente usare la coerenza spaziale a tuo vantaggio e controllare solo gli oggetti che si trovano vicino al giocatore (che hanno più probabilità di colpire / intersecare.

Questo può essere fatto facilmente memorizzando effettivamente quelle informazioni spaziali in una struttura di dati di partizionamento spaziale . Per un gioco semplice suggerirei una griglia perché è praticamente facile da implementare e si adatta bene alla scena dinamica.

Ogni cella / casella contiene un elenco di oggetti racchiusi nel riquadro di delimitazione della griglia. Ed è facile tenere traccia della posizione del giocatore in quelle celle. E per i calcoli della distanza, controlli la distanza del giocatore solo con quegli oggetti all'interno delle stesse celle o nelle vicinanze invece di tutto ciò che è nella scena.

Un approccio più complicato è usare BSP o Octrees.


2
Credo che l'ultima frase della domanda affermi che OP sta cercando altre alternative (sanno di usare la distanza al quadrato).
MichaelHouse

@ Byte56 sì, hai ragione, non l'ho letto.
concept3d

Grazie per la risposta comunque. Aggiungeresti una frase che conferma che anche se quel metodo non ci fornisce una distanza euclidea, è molto accurato nei confronti? Penso che aggiungerebbe qualcosa a qualcuno che viene qui da un motore di ricerca.
Grimshaw,

@Grimshaw Ho modificato la risposta per affrontare il problema originale.
concept3d

@ Byte56 grazie per la segnalazione. Ho modificato la risposta.
concept3d

29

Se hai bisogno di qualcosa che rimanga lineare su qualsiasi distanza (a differenza distance^2) e tuttavia appaia vagamente circolare (a differenza delle Chebyshev quadrate e delle distanze di Manhattan simili a diamanti), puoi fare la media di queste ultime due tecniche per ottenere un'approssimazione di distanza a forma ottagonale:

dx = abs(x1 - x0)
dy = abs(y1 - y0)

dist = 0.5 * (dx + dy + max(dx, dy))

Ecco una visualizzazione (diagramma di contorno) della funzione, grazie a Wolfram Alpha :

Trama di contorno

Ed ecco un diagramma della sua funzione di errore rispetto alla distanza euclidea (radianti, solo primo quadrante):

Grafico errori

Come puoi vedere, l'errore varia dallo 0% sugli assi a circa + 12% nei lobi. Modificando un po 'i coefficienti possiamo arrivare a +/- 4%:

dist = 0.4 * (dx + dy) + 0.56 * max(dx, dy)

inserisci qui la descrizione dell'immagine

Aggiornare

Utilizzando i coefficienti di cui sopra, l'errore massimo sarà compreso tra +/- 4%, ma l'errore medio sarà ancora + 1,3%. Ottimizzato per errore medio zero, è possibile utilizzare:

dist = 0.394 * (dx + dy) + 0.554 * max(dx, dy)

che fornisce errori tra -5% e + 3% e un errore medio di + 0,043%


Durante la ricerca sul web di un nome per questo algoritmo, ho trovato questa simile approssimazione ottagonale :

dist = 1007/1024 * max(dx, dy) + 441/1024 * min(dx, dy)

Si noti che questo è essenzialmente equivalente (sebbene gli esponenti siano diversi: questi danno un errore compreso tra -1,5% e 7,5%, ma può essere massaggiato a +/- 4%) perché max(dx, dy) + min(dx, dy) == dx + dy. Utilizzando questo modulo, le chiamate mine maxpossono essere fattorizzate a favore di:

if (dy > dx)
    swap(dx, dy)

dist = 1007/1024 * dx + 441/1024 * dy

Sarà più veloce della mia versione? Chissà ... dipende dal compilatore e da come ottimizza ciascuno per la piattaforma di destinazione. Suppongo che sarebbe piuttosto difficile vedere alcuna differenza.


3
Interessante, non l'ho mai visto prima! Ha un nome, o semplicemente "media di Chebyshev e Manhattan"?
congusbongus,

@congusbongus Probabilmente ha un nome, ma non so cosa sia. In caso contrario, forse un giorno si chiamerà Crist Distance (hah ... probabilmente no)
bcrist

1
Si noti che le moltiplicazioni in virgola mobile non sono molto efficienti. Ecco perché l'altra approssimazione usa 1007/1024 (che sarà implementato come moltiplicazione di numeri interi seguita da bit shift).
Salterio il

Sì, le operazioni in virgola mobile sono spesso più lente delle operazioni con numeri interi, ma ciò è irrilevante: 0.4 e 0.56 potrebbero essere altrettanto facilmente convertiti per utilizzare le operazioni con numeri interi. Inoltre, sui moderni hardware x86, la maggior parte delle operazioni in virgola mobile (diverse da FDIV, FSQRTe altre funzioni trascendentali) costano essenzialmente le stesse delle loro versioni intere: 1 o 2 cicli per istruzione.
scrive il

1
Sembra molto simile a Alpha max + Beta Min: en.wikipedia.org/wiki/Alpha_max_plus_beta_min_algorithm
drake7707

21

A volte questa domanda può sorgere non a causa del costo dell'esecuzione dei calcoli della distanza, ma a causa del numero di volte in cui il calcolo viene eseguito.

In un grande mondo di gioco con molti attori, è unscalable di mantenere il controllo della distanza tra un attore e tutti gli altri. Come più giocatori, NPC e proiettili entrano nel mondo, il numero di confronti che hanno bisogno di essere fatto crescerà quadratico con O(N^2).

Un modo per ridurre tale crescita è utilizzare una buona struttura di dati per scartare rapidamente gli attori indesiderati dai calcoli.

Stiamo cercando un modo per iterare efficacemente tutti gli attori che potrebbero essere nel raggio d'azione, escludendo la maggior parte degli attori che sono decisamente fuori portata .

Se i tuoi attori sono distribuiti in modo abbastanza uniforme nello spazio mondiale, una griglia di secchi dovrebbe essere una struttura adatta (come suggerisce la risposta accettata). Mantenendo i riferimenti agli attori in una griglia approssimativa, è sufficiente controllare solo alcuni dei secchi vicini per coprire tutti gli attori che potrebbero trovarsi nel raggio d'azione, ignorando il resto. Quando un attore si muove, potresti doverlo spostare dal suo vecchio secchio a uno nuovo.

Per gli attori che sono meno uniformemente distribuiti, un quadrifoglio potrebbe fare di meglio per un mondo bidimensionale, oppure un ottetto sarebbe adatto per un mondo tridimensionale. Si tratta di strutture più generiche che possono partizionare in modo efficiente ampie aree di spazio vuoto e piccole aree contenenti molti attori. Per gli attori statici c'è il partizionamento dello spazio binario (BSP), che è molto veloce da cercare ma troppo costoso per essere aggiornato in tempo reale. I BSP separano lo spazio usando i piani per tagliarlo ripetutamente a metà e possono essere applicati a qualsiasi numero di dimensioni.

Naturalmente ci sono delle spese generali per mantenere una struttura simile per i tuoi attori, specialmente quando si muovono tra le partizioni. Ma in un grande mondo con molti attori ma con piccoli intervalli di interesse, i costi dovrebbero essere di gran lunga inferiori a quelli sostenuti da un ingenuo confronto con tutti gli oggetti.

La considerazione di come cresce la spesa di un algoritmo man mano che riceve più dati è cruciale per la progettazione di software scalabile. A volte è sufficiente scegliere semplicemente la struttura dati corretta . I costi sono di solito descritte utilizzando notazione O-grande .

(Mi rendo conto che questa non è una risposta diretta alla domanda, ma può essere utile per alcuni lettori. Mi scuso se ho perso tempo!)


7
Questa è la risposta migliore Non c'è nulla da ottimizzare nella funzione distanza; bisogna solo usarlo meno spesso.
Sam Hocevar,

3
La risposta accettata copre anche il partizionamento spaziale, altrimenti la tua risposta è davvero ottimale. Grazie.
Grimshaw,

Ho trascorso molto tempo a leggere la tua risposta. Grazie Joey.
Patrick M,

1
Questa è la risposta migliore e l' unica che si concentra sul problema reale piuttosto che sull'aringa rossa delle prestazioni della funzione di distanza. La risposta accettata potrebbe anche coprire il partizionamento spaziale, ma è a parte; si concentra sul calcolo della distanza. Il calcolo della distanza non è il problema principale qui; l'ottimizzazione del calcolo della distanza è una non soluzione a forza bruta che non si ridimensiona.
Maximus Minimus

Potresti spiegare perché il numero di confronti sarebbe esponenziale? Ho pensato che sarebbe quadratico, confrontando ogni attore tra loro durante ogni periodo di tempo.
Petr Pudlák,

4

Che ne dici della distanza di Chebyshev? Per i punti p, q è definito come segue:

distanza

Quindi per i punti (2, 4) e (8, 5), la distanza di Chebyshev è 6, come | 2-8 | > | 4-5 |.

Inoltre, sia E la distanza euclidea e C la distanza di Chebyshev. Poi:

Distance2

Il limite superiore probabilmente non sarà molto utile poiché dovresti calcolare la radice quadrata, ma il limite inferiore potrebbe essere utile - ogni volta che la distanza di Chebyshev è abbastanza grande da essere fuori portata, anche la distanza euclidea deve essere, salvandoti dal doverlo calcolare.

Il compromesso, ovviamente, è che se la distanza di Chebyshev è nel raggio d'azione, dovrai comunque calcolare la distanza euclidea, sprecando tempo. Solo un modo per scoprire se sarà una vincita netta!


1
È inoltre possibile utilizzare la distanza di Manhattan come limite superiore.
congusbongus,

1
Abbastanza vero. Suppongo che da lì sia solo un salto, un salto e un salto alla "media di Chebyshev e Manhattan" come suggerito da Bcrist.
Tetrinità,

2

Un'ottimizzazione locale molto semplice consiste semplicemente nel controllare prima una singola dimensione.

Questo è :

distance ( x1, y1 , x1, y2) > fabs (x2 - x1)

Quindi il solo controllo fabs (x2 - x1)come primo filtro può dare un guadagno apprezzabile. Quanto dipenderà dalla dimensione del mondo rispetto alle gamme pertinenti.

Inoltre, è possibile utilizzarlo come alternativa alla struttura dei dati di partizionamento spaziale.

Se tutti gli oggetti rilevanti sono ordinati in un elenco in ordine di coordinate x, gli oggetti vicini devono essere vicini nell'elenco. Anche se l'elenco diventa fuori servizio a causa del mancato mantenimento degli oggetti mentre si spostano, dati i limiti di velocità noti, è comunque possibile ridurre la sezione dell'elenco da cercare per gli oggetti vicini.


2

In passato sono stati compiuti sforzi per ottimizzare sqrt. Sebbene non si applichi più alle macchine di oggi, ecco un esempio del codice sorgente di Quake, che utilizza il numero magico 0x5f3759df :

float Q_rsqrt( float number )
{
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y  = number;
  i  = * ( long * ) &y;  // evil floating point bit level hacking
  i  = 0x5f3759df - ( i >> 1 ); // what the hell?
  y  = * ( float * ) &i;
  y  = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
  // y  = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration (optional)
  // ...
  return y;
}

Una spiegazione dettagliata di ciò che sta succedendo qui può essere trovata su Wikipedia.

In breve, si tratta di alcune iterazioni del metodo di Newton (un algoritmo numerico che migliora iterativamente una stima), con il numero magico utilizzato per fornire una stima iniziale ragionevole.

Come sottolinea Travis, questo tipo di ottimizzazione non è più utile per le architetture moderne. E anche se lo fosse, potrebbe solo fornire una velocità costante al tuo collo di bottiglia, mentre la riprogettazione algoritmica potrebbe ottenere risultati migliori.


2
Questa non è più un'ottimizzazione utile. Quasi tutte le architetture per PC di livello consumer che è possibile acquistare al giorno d'oggi dispongono di istruzioni sqrt ottimizzate per l'hardware che eseguono la radice quadrata in un ciclo di clock o inferiore. Se hai davvero bisogno del sqrt più veloce possibile, usi l'istruzione sqrt a virgola mobile x86 simd: en.wikipedia.org/wiki/… Per cose come shader su GPU, chiamare sqrt comporterà automaticamente tale istruzione. Sulla CPU, suppongo che molti compilatori implementino sqrt tramite SIMD sqrt se disponibile.
TravisG,

@TravisG Sì, vale la pena menzionarlo, quindi ho aggiornato la risposta. Questa risposta è stata fornita solo per divertimento e interesse storico!
joeytwiddle,
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.