Big O conta davvero?


17

Nel caso peggiore del mondo accademico, Big O viene insegnato su tutto il resto. Rispetto alla complessità dello spazio, alla normale analisi dei casi, alla semplicità rispetto alla complessità, ecc.

In particolare per la programmazione dei giochi e l'industria, cosa conta davvero di più e perché?

I riferimenti sarebbero molto utili.


Big-O = Ottimizzazione. Mi ci è voluto un po 'per capire cosa fosse il big-0.
Attaccando

12
Big-O non è "ottimizzazione". Big-O è una forma di analisi che ti dice come si comporteranno i diversi algoritmi, in termini di efficienza, man mano che aumenta il numero di elementi su cui agiscono. Vedi en.wikipedia.org/wiki/Big_O_notation per maggiori dettagli.
ZorbaTHut,

2
Posso assicurarti che le persone che hanno inventato ocre e BSP / PVS sapevano tutto di big-O. Alla fine, l'unica cosa che conta sono le prestazioni dell'applicazione. Ma per arrivarci devi considerare ogni genere di cose, inclusa la complessità asintotica degli algoritmi che gestiscono molti dati.
drxzcl,

1
Ricorda la regola di quando ottimizzare: 1) Non farlo. 2) (solo per esperti) Non farlo ancora.
Zaratustra,

Bene, prima di tutto Big O può essere utilizzato per lo spazio o la complessità computazionale, quindi "sopra ogni altra cosa" non è esattamente vero. In secondo luogo, Big O è di solito molto più semplice da calcolare per qualcosa rispetto alla normale analisi del caso e verrebbe usato come un rapido controllo mentale per capire se stai facendo qualcosa di sbagliato. Se disegnare uno sprite richiede O (2 ^ n) tempo, probabilmente dovresti scegliere un algoritmo diverso. Se si desidera un approccio più pratico alla progettazione del software, esaminare le pratiche SE piuttosto che CS. CS è di natura teorica, mentre SE è più basata sull'industria.
Deleter,

Risposte:


25

Come con ogni altra domanda relativa a "Qual è il vero percorso", questi sono tutti gli strumenti nella tua cassetta degli attrezzi e ci sono casi in cui big-O vince su tutto e luoghi dove non importa (tm).

"Non" scriveresti mai un risolutore di fisica senza preoccuparti del big-O. Non implementeresti un algoritmo di ordinamento (per qualsiasi set di dati tranne il più piccolo) senza preoccuparti di esso. Se stai scrivendo un gioco in rete, ti preoccuperai del modo in cui le prestazioni e il traffico di rete si ridimensionano per utente.

Potresti non essere così preoccupato per il big-O quando, beh, non riesco davvero a pensare a un momento, ma sono sicuro che ce ne sono. :) Per fortuna, la maggior parte delle cose che facciamo nei giochi si ridimensiona in modo lineare; vuoi leggere un file dal disco? Ci vorrà un tempo linearmente proporzionale alla dimensione del file (attualizzando il fattore costante di ricerca e possibili ramificazioni della dimensione del settore).

Tuttavia, cosa succede se si desidera trovare un'entità specifica nell'elenco delle entità? Questa è una ricerca lineare ogni volta che lo fai. Se hai bisogno di trovare il giocatore una volta per ogni entità del mondo, questo approccio ti ucciderà per tutti tranne che per i giochi più banali, e anche allora probabilmente vale la pena "ottimizzare" questa ricerca per essere un tempo costante (ad es. Memorizzare l'indice o un puntatore al giocatore da qualche parte), dandoti più tempo per fare altre cose che sono effettivamente visibili al giocatore.

Immagino che riassuma, però; ogni volta che il processore sta facendo qualcosa che non è direttamente rappresentabile per il giocatore, sta perdendo tempo. Massimizzare il tempo in cui il processore sta elaborando i dati che verranno mostrati al lettore sta massimizzando il WOW! stai dando al giocatore.


8
Questo. È importante comprendere le caratteristiche prestazionali del tuo codice. Non sai mai quando un designer utilizzerà qualcosa che hai aggiunto in un modo che non ti aspettavi, e improvvisamente il pezzo di codice che pensavi avrebbe dovuto gestire solo 5 elementi ora gestisce 5000 e viene eseguito il ping 100 volte un frame. Lo ottimizzi? Puoi? Quanti sono effettivamente ragionevoli? Un profilo ti dirà solo quanto è lento, non perché. Conoscere la complessità ti dirà se è necessario ottimizzare il codice o sostituirlo con qualcosa di diverso.
JasonD,

3
Concordato. Le università ti insegnano il 'Big-O' perché gestisce molti dei problemi che dovrai affrontare. Quando ti viene chiesto "oh, possiamo renderlo infinito anziché solo 5? i tester odiano la limitazione 'è quando l'addestramento paga. Non dovresti semplicemente dire "no, non posso". È fondamentale essere in grado di risolvere il problema e dire 'sì, posso'. Il tuo gioco ha bisogno di una galassia ricercabile? 'Nessun problema'. Quei milioni di unità devono essere ordinate? 'Nessun problema'. 'Non ho studiato abbastanza' semplicemente non lo taglia.
Rushyo,

"Potresti non essere così preoccupato per big-O quando ..." durante l'elaborazione delle chiavi di input. Ho ereditato un sistema di input che ha fatto di tutto per risolvere i key-> mapping delle azioni in tempo costante, usando una tabella di ricerca. Il passaggio a una ricerca lineare di un array di coppie (chiave, azione) ha salvato la memoria e non ha avuto alcun impatto sulle prestazioni, dal momento che gli utenti raramente premono più di pochi tasti un frame e l'array di solito è lungo solo 20-30 elementi. Ci consente inoltre di aggiungere (chiave, chiave, azione) per gli accordi.

Joe, certo, anche se questo è un diverso tipo di preoccupazione. Vuoi O (1) dove il fattore costante è alto o O (n) con una piccola 'n' e un fattore costante basso? Conoscere big-O in questo caso non è un problema, ma può aiutarti a capire se la soluzione ha o meno senso per le circostanze in cui verrà utilizzata.
dash-tom-bang

13

La mia regola empirica è che a meno che tu non sia O (spaventoso), i tuoi altri problemi sono più pertinenti.

L'altra mia regola empirica è che i dati sono re. A meno che non modifichi il tuo codice con un set di dati realistico, stai solo facendo ipotesi.

Modifica: per entrare un po 'più in dettaglio, il tuo grande O non è così importante poiché (almeno nella mia esperienza) la maggior parte dei tuoi set di dati sono relativamente piccoli. Probabilmente non ti interessa il limite superiore delle prestazioni quando lavori con una struttura di dati con meno di qualche centinaio di elementi. E se le tue liste hanno più di 100k elementi, devi davvero considerare tutti gli aspetti dei tuoi algoritmi. Questo, e dalla mia esperienza, la memoria è più un fattore limitante della velocità della CPU. Un algoritmo di hogging della memoria più veloce potrebbe non essere buono come uno più snello ma più lento a seconda dei casi d'uso.


8

Big O è importante per la maggior parte del tempo, ma a volte un algoritmo apparentemente "peggiore" in teoria risulta essere molto più veloce in pratica.

Guarda un ottimo esempio di Tony Albrecht: http://seven-degrees-of-freedom.blogspot.com/2010/07/question-of-sorts.html

Lo trovi ovunque nello sviluppo di giochi in cui il numero di elementi nell'operazione è così grande che un algoritmo molto diverso è più veloce, o così piccolo che un algoritmo più stupido è sufficiente (o si inserisce nella cache così bene da ignorare l'efficienza del migliore algoritmo).

Il problema con Big O è che si tratta di una designazione generica della complessità dell'attività e non tiene conto della complessità dell'hardware di destinazione moderno, né offre informazioni dettagliate sui tempi di installazione.

In molti casi, la migliore soluzione ottimale è in due fasi. In pratica, gli sviluppatori di giochi tendono a puntare su algoritmi a basso O ma bilanciati rispetto allo sviluppo o al debug dei costi nel tempo. Una volta che hai una soluzione ragionevole, devi sempre vedere come l'hardware gestisce l'attività e come consentire all'hardware di fare di più in meno tempo.


"Il problema con Big O" è che le persone sembrano dimenticare che si tratta di complessità algoritmica rispetto alle prestazioni rispetto a grandi dimensioni di set di dati. Nei giochi non usiamo (di solito) quei valori di N, quindi dobbiamo preoccuparci degli altri pezzi del puzzle. Ho il sospetto che l'ordinamento a bolle superi sempre le prestazioni veloci quando hai un elenco di due elementi.
dash-tom-bang,

7

Quando sto codifica a motore, sono spesso preoccupati solo con un fisso n: Ho già una partizione spaziale che limita il numero di oggetti che riceve update(), physics()erender() a circa quelli su schermo e le zone circostanti. La dimensione massima del batch è di solito abbastanza ben definita per gioco, sebbene invariabilmente sia un po 'più grande di quanto tu abbia pianificato.

In questo caso non mi occupo tanto del big-O quanto del moltiplicatore del fattore costante e dei termini di ordine inferiore. Per una funzione con runtime come a*n^2 + b*n + c(che è O(n^2)), spesso mi preoccupo molto di ridurre ae possibilmente eliminare c. Un costo di installazione o smontaggio cpuò diventare proporzionalmente grande rispetto a un piccolo n.

Tuttavia, questo non vuol dire che big-O (o più particolarmente big-theta ) sia un ottimo indicatore di odore di codice. Vedere un O(n^4)da qualche parte, o peggio ancora unO(k^n) tempo geometrico, ed è tempo di assicurarti di prendere in considerazione altre opzioni.

In genere sono molto più preoccupato per l'ottimalità del big-O e per saltare attraverso i cerchi per trovare algoritmi con big-O inferiore quando ho a che fare con strumenti per la creazione di dati. Mentre il numero di oggetti in un determinato livello / area di streaming è generalmente ben definito, il numero totale di oggetti / risorse artistiche / file di configurazione / ecc in un intero gioco potrebbe non esserlo. È anche un numero molto più grande. Anche eseguendo una creazione di dati paralleli, aspettiamo ancora nell'ordine di un minuto (lo so, piagnucolare - i dati per console possono richiedere ore - siamo per lo più piccoli giochi portatili) per passare attraverso un jam data-clean && jam dataciclo.

Per fare un esempio specifico: questo è stato davvero fuori controllo con un algoritmo di streaming di piastrelle di sfondo che riproduce le tessere 8x8 256 colori. È utile condividere i buffer di streaming tra "livelli" di sfondo e potremmo averne fino a 6 in un dato livello condividendo lo stesso buffer. Il problema è che la stima della dimensione del buffer necessaria si basa sulle possibili posizioni di tutti e 6 i livelli - e se si tratta di una larghezza / altezza / velocità di scorrimento del numero primo, si inizia rapidamente ad iniziare una ricerca esaustiva - che inizia ad avvicinarsiO(6^numTiles)- che è in molti casi nella categoria "più lungo dell'universo sarà intorno". Fortunatamente la maggior parte dei casi ha solo 2-3 livelli, ma anche in questo caso, siamo in grado di superare la mezz'ora di autonomia. Al momento, campioniamo un piccolo sottoinsieme di queste possibilità, aumentando la granularità fino a quando non è trascorso un determinato periodo di tempo (o abbiamo completato l'attività, che può accadere per piccole configurazioni a doppio strato). Aumentiamo un po 'questa stima in base alle statistiche precedenti sulla frequenza con cui ci siamo dimostrati sbagliati, quindi aggiungiamo un po' di riempimento extra per una buona misura.

Un altro esempio divertente: su un gioco per PC un po 'di tempo fa, l'ingegnere capo ha sperimentato per un po' con skip list . L'overhead di memoria finisce per causare più effetti cache, il che aggiunge una sorta di moltiplicatore non costante all'intera faccenda - quindi non sono affatto una buona scelta per i piccoli n. Ma per elenchi più grandi in cui le ricerche sono frequenti, offrono un vantaggio.

(Trovo spesso che l'algoritmo ingenuo sia big-O superiore, più veloce su insiemi di dati più piccoli e più facile da capire; quelli più interessanti / complessi (ad esempio patricia trie) sono più difficili da comprendere e mantenere per le persone, ma prestazioni più elevate su più grandi set di dati.)


4

Può essere utile, ma può anche essere irrilevante. Prendi, ad esempio, il mio gioco più recente, che è una specie di clone di Smash TV. Gioco dall'alto verso il basso, i mostri si riversano dai lati, li spari.

Ora ci sono molti modi intelligenti per determinare le collisioni. Puoi usare KDtrees per dividere lo spazio in modo da non testare proiettili contro mostri che non potrebbero colpire. E, sicuramente, avrei potuto essere intelligente e avrei potuto farlo.

Ma mi sentivo pigro, quindi ho solo confrontato ogni proiettile con ogni mostro. Anche nelle situazioni più frenetiche, il codice di collisione utilizzava molto meno del 10% della CPU del gioco a 60 fps. Big-O: non importante.

Allo stesso modo, ho avuto un gioco in stile 4x in cui hai costruito città sulle isole e a volte le città sono state distrutte. Avrei potuto essere intelligente e tentare di sottrarre il reddito della città distrutta dalle variabili del reddito. Ma non l'ho fatto. Ho appena cancellato il reddito e ricalcolato da zero ogni volta che qualcosa è cambiato. Totalmente irrilevante in termini di CPU.

Big-O è tanto importante nei giochi quanto in ogni altra cosa: vale a dire, assolutamente irrilevante, fino a quando non diventa critico.

Vai a scrivere un po 'di codice. Se è troppo lento, quindi profilalo.


3

L'analisi Big-O è importante, ma non è la prima cosa a cui pensare nello sviluppo del gioco. Dal momento che la creazione di giochi comportava un sacco di codice complicato, consiglierei sempre Code Simplicity come primo criterio per un algoritmo. Gli algoritmi con contabilità complicata ti fanno perdere tempo.

Penso che sia davvero importante che il tuo gioco funzioni sempre a 60 fps durante lo sviluppo. Quando ti immergi sotto, la prima cosa da fare è eseguire un profiler. Una volta trovato il collo di bottiglia, lo attacchi. Molte volte devi fare cose non codificanti come dire ai progettisti di livelli di mettere meno cose in un'area (e dare loro strumenti per quello).

A volte identifichi effettivamente del codice che deve essere velocizzato. Trovo che sia divertente ingegneria! Vorrei avere più opportunità per farlo. E ovviamente vuoi iterare cambiando una cosa alla volta e misurando le prestazioni. I problemi tipici che trovo sono:

  1. Assicurati di non chiamare nuovo o malloc ogni frame (questo è sempre il problema n. 1)
  2. Ridurre il lavoro: meno ray cast, meno ragazzi, ecc.
  3. Problemi di tipo algoritmo Big-O
  4. Coerenza della cache: inserisci le cose negli array piuttosto che nella memoria sparsa
  5. Non utilizzare STL in modalità debug. (e vuoi sempre che funzioni la modalità debug)

2

La notazione Big-O è per definizione complessità asintotica - cioè, mostra come scala dei tempi quando N (o qualunque variabile tu abbia) diventa "molto" grande. Per ripetere il commento di Tetrad (che ho ribadito) "data is king". Se N è "molto grande" nella tua situazione specifica, importa, se N è "molto piccolo" non importa. L'esperienza e la pratica ti daranno un'idea di come quantificare "molto grande" e "molto piccolo".

Ovviamente, profila sempre per primo e ottimizza per ultimo (a meno che tu non stia facendo uno studio di fattibilità delle funzionalità).


1

L'importanza di Big-O nel tuo software è O (N 2 ). Man mano che N cresce, l'importanza di disporre dell'algoritmo giusto cresce ancora di più. :)


Ciò non dipende dalla frequenza con cui viene chiamato quell'algoritmo ...?
Bobobobo,

Di qualche grado. Ma se ci vogliono 3 giorni per l'esecuzione, probabilmente non importa se lo chiami una volta sola. :)
Kylotan,

1

Big-O è solo una linea guida - qualcosa che ti dice le prestazioni approssimative che puoi aspettarti da un algoritmo - e come dovresti aspettarti che le prestazioni si ridimensionino mentre aumenti le dimensioni del set di dati . Devi ricordare due cose principali riguardo a Big-O:

1) Se hai due algoritmi che fanno principalmente la stessa cosa ma uno ha una O migliore, probabilmente dovresti scegliere quello (ovviamente)

2) Big O si occupa dell'analisi asintotica . Big-O entra in gioco solo quando n è grande . Ad esempio, un algoritmo O (n) può essere molto simile nelle prestazioni a un O (n ^ 2) uno .. per piccoli n . Se stai parlando di un algoritmo che richiede n ^ 2 operazioni per vertice, ma n = 2 o n = 3, allora non c'è molta differenza tra un algoritmo O (n ^ 2) (prendendo rispettivamente 4 e 9 operazioni) e una O (n) una (2 e 3 operazioni rispettivamente). Tuttavia, se n = 9, allora stai improvvisamente parlando di 81 operazioni per l'algoritmo O (n ^ 2) e solo 9 per quello O (n) - una differenza maggiore - e se n = 100, allora sei parlando di 100 operazioni contro 10000 - una differenza molto più grande.

Quindi devi sempre considerare Big-O sotto questo aspetto: ha lo scopo di confrontare gli algoritmi che fanno la stessa cosa in base alle peggiori prestazioni di n che diventa grande . Le differenze tra gli algoritmi possono essere quasi trascurabili quando n è molto piccolo.


0

Non ho riferimenti, ma Big O è almeno utile per essere a conoscenza quando si analizza un problema e una discussione. D'altra parte, ovviamente, se la versione O (log n) ha un modo O più coinvolto rispetto alla versione O (n), è un confronto controverso. E come per tutto, c'è sempre un compromesso. La complessità dello spazio potrebbe essere un problema, sebbene ciò possa essere espresso anche in O in generale. Analisi del caso normale ... meno, in quanto non si desidera che i valori anomali aumentino. La semplicità rispetto alla complessità, secondo me, è relativamente inutile nello sviluppo del gioco in quanto la velocità è quasi sempre un problema, quindi a meno che la semplicità non comporti accelerazioni (ma quindi significa che il tuo caso complesso era sbagliato per ragioni sbagliate) la semplicità dovrà andare fuori dalla finestra a favore della velocità. Ma Big O è sicuramente utile,


0

Quando prototipi una funzione di gioco o un aspetto di un gioco, non dovresti preoccuparti di ottimizzarla affatto .

Nel corso della prototipazione e dell'apprendimento delle idiosincrasie di tale funzionalità, le ottimizzazioni necessarie diventeranno ovvie e verranno integrate nel progetto finale come la 2a natura ... il più delle volte.

Non sudare.


"Quando prototipi una funzione di gioco o un aspetto di un gioco, non dovresti preoccuparti di ottimizzarla affatto." Questo è vero a volte ma non sempre. Alcuni giochi, come Dead Rising, si basano su una rapida esecuzione per rendere fattibile la meccanica di gioco principale - centinaia di zombi in tempo reale -.

Quale percentuale di sviluppo del gioco è la prototipazione? Alla fine vuoi spedire qualcosa , giusto?
dash-tom-bang,

0

Non dovrebbe essere il tutto e la fine di tutto. Ma aiuta a risolvere ovvi problemi che potrebbero causare hit di performance; perché usare qualcosa in O (n ^ 2) time, quando puoi fare la stessa cosa in O (log n) time?

Penso che si applichi ai giochi più della maggior parte degli altri settori, poiché il mercato è quello che noterebbe maggiormente i problemi di velocità. A qualcuno che utilizza un elaboratore di testi non importerà se si verifica un ritardo di mezzo secondo per l'esecuzione dell'azione X, ma i giocatori probabilmente passeranno "omg omg game Y è così lento che ci vogliono anni per compiere l'azione Z".


0

Nello sviluppo del gioco (e della maggior parte degli altri), ci lamentiamo di un'operazione aggiuntiva eseguita per loop:

for (int i = 0; i < array.length; i ++) { /* ... */ }

vs.

for (int i = 0, l = array.length; i < l; i ++) { /* ... */ }

La maggior parte dei giochi moderni ha fisica e troverai il problema della simulazione n-body . In un algoritmo ingenuo, è O (n ^ 2), ma c'è un'ottimizzazione che lo rende O (n log n) (ma sacrifica un po 'di precisione).

Potresti dire che non stai programmando interazioni di gravità e particelle, ma che dire del comportamento di squadra di un esercito (di zombi) in cui si muovono in dipendenza da altre posizioni (in una parola più specifica: sciamare)?

In un algoritmo convenzionale di rilevamento delle collisioni, la complessità temporale è O (n ^ 2), come l'n-body. Tuttavia, esiste un modo migliore: separare il mondo da molte piccole parti in modo che solo gli oggetti all'interno della stessa parte vengano rilevati in caso di collisione. Vedi http://www.videotutorialsrock.com/opengl_tutorial/collision_detection/text.php .

Se il tuo gioco è programmabile tramite script, NON far scrivere allo script O (n ^ 2) (e versioni successive) algoritmi di scricchiolio nello script, come la ricerca nella borsa dell'utente. Crea invece una funzione integrata nel codice.


1
Entrambi i tuoi esempi di codice sono O (n). Le discussioni su Big-O non hanno nulla a che fare con "un'operazione aggiuntiva per ciclo", ma piuttosto "una ricerca aggiuntiva attraverso tutto per iterazione del ciclo su tutto".
dash-tom-bang,

-2

Nel mondo reale contano solo le prestazioni grezze. Ora, il Big-O di un algoritmo potrebbe servire come prima indicazione di cosa usare, ma a seconda dell'hardware l'implementazione potrebbe essere terribilmente inefficiente. Ad esempio, fare una ricerca lineare può spesso essere più veloce di una ricerca binaria perché si ottiene un accesso alla memoria lineare e nessun ramo.

Inoltre, a causa dell'attuale direzione nelle piattaforme e architetture multi-thread, Big-O sta perdendo molto significato poiché sta solo prendendo in considerazione la scalabilità verticale della memoria o dei tocchi di dati per operazione anziché tenere conto anche di come l'algoritmo scala con un numero maggiore di thread.


3
Questo non è corretto, la notazione Big O viene utilizzata per mostrare i limiti superiori degli algoritmi paralleli esattamente come gli algoritmi lineari. Big O può essere usato per architetture simultanee di lettura / scrittura simultanea ecc. Puoi anche fare cose folli come l'ordinamento in O (1) con n ^ 2 processori hah
David Young

David, hai qualche esempio di vita reale? Voglio dire, posso anche Big-O il numero di mele che un gruppo di persone può trasportare, ma ciò non significa che sia usato o utile. Dalla mia esperienza, la maggior parte delle volte gamedev sceglie i suoi algoritmi (paralleli) in base alle prestazioni non elaborate, non alle loro funzioni di crescita.
Jasper Bekkers,

3
"ordinamento in O (1) con n ^ 2 processori" In genere ritengo che questo utilizzo di O sia fuorviante poiché l'utilizzo delle risorse è ancora O (n ^ 2), indipendentemente dal modo in cui si affetta il problema. Un numero maggiore di thread non significa solo un numero maggiore di cicli cpu al secondo.
Richard Fabian,

L'ordinamento in O (1) con n ^ 2 processori non è l'esempio migliore, questo tipo di notazione Big-O è probabilmente visto più spesso nel mondo accademico. Qualcosa del genere cs.bu.edu/~best/crs/cs551/homeworks/hw1/pram.html Algoritmi paralleli più realistici possono usare processori log (n). Questo tipo di cose è più adatto a sovraccaricare l'elaborazione della GPU o il supercomputer dove sono disponibili centinaia di core.
David Young,

err intendevo scaricare, non sovraccaricare. Non riesco più a modificare il mio commento originale.
David Young,
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.