Grande O degli array JavaScript


105

Gli array in JavaScript sono molto facili da modificare aggiungendo e rimuovendo elementi. In qualche modo maschera il fatto che la maggior parte degli array di lingue sono di dimensioni fisse e richiedono operazioni complesse per il ridimensionamento. Sembra che JavaScript renda facile scrivere codice array con prestazioni scadenti. Questo porta alla domanda:

Quali prestazioni (in termini di grande complessità del tempo O) posso aspettarmi dalle implementazioni JavaScript per quanto riguarda le prestazioni dell'array?

Presumo che tutte le implementazioni JavaScript ragionevoli abbiano al massimo le seguenti grandi O.

  • Accesso - O (1)
  • Aggiunta - O (n)
  • Anteprime - O (n)
  • Inserimento - O (n)
  • Cancellazione - O (n)
  • Scambio - O (1)

JavaScript ti consente di precompilare un array fino a una certa dimensione, utilizzando la new Array(length)sintassi. (Domanda bonus: sta creando un array in questo modo O (1) o O (n)) Questo è più simile a un array convenzionale e, se usato come array pre-dimensionato, può consentire l'aggiunta di O (1). Se viene aggiunta la logica del buffer circolare, è possibile ottenere O (1) anteposto. Se viene utilizzato un array in espansione dinamica, O (log n) sarà il caso medio per entrambi.

Posso aspettarmi prestazioni migliori per alcune cose rispetto alle mie ipotesi qui? Non mi aspetto che nulla sia delineato in nessuna specifica, ma in pratica potrebbe essere che tutte le principali implementazioni utilizzino array ottimizzati dietro le quinte. Ci sono array in espansione dinamica o altri algoritmi che migliorano le prestazioni al lavoro?

PS

Il motivo per cui mi chiedo questo è che sto ricercando alcuni algoritmi di ordinamento, la maggior parte dei quali sembra presumere che l'aggiunta e l'eliminazione siano operazioni O (1) quando descrivo la loro O grande complessiva.


6
Il costruttore di array con una dimensione è praticamente inutile nelle moderne implementazioni JavaScript. Non fa quasi nulla in quella forma a parametro singolo. (Si imposta .lengthma questo è tutto.) Gli array non sono molto diversi dalle semplici istanze di Object.
Pointy

3
L'impostazione della lengthproprietà e la pre-allocazione dello spazio sono due cose completamente diverse.
Pointy

1
@Pointy: Mi aspetto troppo quando mi aspetto che l'impostazione array[5]su a new Array(10)sia O (1)?
Kendall Frey

1
Sebbene ECMAScript non definisca come viene implementato un oggetto Array (definisce solo alcune regole semantiche), è assolutamente possibile che diverse implementazioni si ottimizzino per i casi previsti (es. Avere un supporto "array reale" per array di dimensioni inferiori a n ). Non sono così esperto sulle implementazioni, ma sarei davvero sorpreso se ciò non fosse fatto da qualche parte ...

5
@KendallFrey "La migliore risposta" probabilmente scriverà alcuni casi di test jsperf per diversi modelli di accesso n / e vedrà cosa ne deriva ;-)

Risposte:


111

NOTA: sebbene questa risposta fosse corretta nel 2012, i motori utilizzano oggi rappresentazioni interne molto diverse sia per gli oggetti che per gli array. Questa risposta può o non può essere vera.

A differenza della maggior parte dei linguaggi, che implementano array con, beh, array, in Javascript gli array sono oggetti e i valori sono memorizzati in una tabella hash, proprio come i normali valori degli oggetti. Come tale:

  • Accesso - O (1)
  • Aggiunta - Amortized O (1) (a volte è necessario ridimensionare la tabella hash; di solito è richiesto solo l'inserimento)
  • Prepending - O (n) via unshift, poiché richiede la riassegnazione di tutti gli indici
  • Inserimento - Ammortizzato O (1) se il valore non esiste. O (n) se si desidera spostare i valori esistenti (ad esempio, utilizzando splice).
  • Cancellazione - Ammortizzato O (1) per rimuovere un valore, O (n) se si desidera riassegnare gli indici tramite splice.
  • Scambio - O (1)

In generale, l'inserimento o il disinserimento di qualsiasi chiave in un dict viene ammortizzato con O (1), e lo stesso vale per gli array, indipendentemente da quale sia l'indice. Qualsiasi operazione che richiede la rinumerazione dei valori esistenti è O (n) semplicemente perché è necessario aggiornare tutti i valori interessati.


4
Non dovrebbe essere anteposto O (n)? Poiché tutti gli indici devono essere spostati. Lo stesso per l'inserimento e la cancellazione (a indice arbitrario e sposta / comprimi gli elementi).
nhahtdh

2
Inoltre, è lengthimpostato sulla mutazione Array, o getsu di esso otterrà la lunghezza e forse lo memorizzerà?
alex

27
Vale la pena ricordare che questa risposta non è più corretta. I motori moderni non memorizzano gli array (o gli oggetti con chiavi intere indicizzate) come tabelle hash (ma come bene ... gli array come in C) a meno che non siano sparsi. Per iniziare, ecco un benchmark "classico" che lo illustra
Benjamin Gruenbaum,

4
Questo è definito dallo standard o è solo un'implementazione comune nei motori JS? Che mi dici di V8?
Albert

4
@BenjaminGruenbaum sarebbe bello se potessi sviluppare un po 'su come vengono archiviati. Oppure fornisci alcune fonti.
Ced

1

garanzia

Non esiste una garanzia di complessità temporale specificata per qualsiasi operazione su array. Le prestazioni degli array dipendono dalla struttura dati sottostante scelta dal motore. I motori potrebbero anche avere rappresentazioni diverse e passare da una all'altra a seconda di determinate euristiche. La dimensione dell'array iniziale potrebbe o non potrebbe essere tale euristica.

la realtà

Ad esempio, V8 utilizza (ad oggi) sia tabelle hash che elenchi di array per rappresentare gli array. Ha anche varie rappresentazioni differenti per gli oggetti, quindi gli array e gli oggetti non possono essere confrontati. Pertanto l'accesso all'array è sempre migliore di O (n) e potrebbe anche essere veloce come l'accesso all'array C ++. L'aggiunta è O (1), a meno che non si raggiunga la dimensione della struttura dati e questa debba essere scalata (che è O (n)). La preparazione è peggio. La cancellazione può essere anche peggiore se fai qualcosa come delete array[index](non!), Poiché ciò potrebbe costringere il motore a cambiare la sua rappresentazione.

consigli

Usa array per strutture dati numeriche. Ecco a cosa sono destinati. È per questo che i motori li ottimizzeranno. Evita gli array sparsi (o, se devi, aspettati prestazioni peggiori). Evita gli array con tipi di dati misti (poiché ciò rende le rappresentazioni interne più complesse ).

Se vuoi davvero ottimizzare per un determinato motore (e versione), controlla il suo codice sorgente per la risposta assoluta.


Aspetta un secondo, possiamo avere array con tipi di dati misti? Javascript è fantastico!
Anurag

@Anurag esattamente, ma nel 99% dei casi non avresti bisogno di questa funzione
Desiigner
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.