2014-06-23 9 views
7

Fondamentalmente, ho bisogno di creare una tabella di ricerca con ID interi non consecutivi. Mi chiedo se, in termini di velocità di ricerca, generalmente preferisco usare uno dict con chiavi integer o usare uno molto lungo list con molti indici vuoti. Mi sembra che un list potrebbe essere ancora più veloce, poichè Python dovrebbe sapere esattamente dove cercare, ma mi chiedo se ci sono dei processi di backend con lo dict per compensare e se i requisiti di memoria aggiuntivi per quegli spazi vuoti list annullerebbero il (probabilmente) più rapido guadagno di velocità di list. Esistono alternative a list se dict s che potrebbero essere più adatte a questo?Meglio usare il dettt delle chiavi integer o una lista molto lunga?

ho visto questa domanda, ma non del tutto rispondere miniera: Dictionary access speed comparison with integer key against string key

ETA: sto implementando le tabelle di ricerca come questo per due volte nel mio programma. Un'istanza vede un id massimo di 5.000 con 70-100 oggetti popolati; l'altro ha un id massimo di 750 con 20-30 popolati.

+10

A meno che non si tratti di codice estremamente sensibile alle prestazioni, non mi preoccuperei. Usa un 'dict' - semanticamente è molto più vicino a quello che stai cercando di ottenere. – sapi

risposta

8

Per rispondere alla tua domanda circa dict vs list che avrebbe dovuto dare l'esatto informazioni sul numero di elementi, il numero di elementi mancanti, ecc, in modo da tha potremmo stimare esattamente l'utilizzo della memoria della struttura dati due e prova a prevedere e/o verificare le loro prestazioni.

In generale un dict di N coppie di valori-chiave richiede un po 'più memoria piuttosto di un list con N valori:

  • Il dict deve tenere traccia delle chiavi
  • Il dict è mai superiore 2/3 completo. Quando ciò accade, la memoria allocata viene raddoppiata (è necessario che O (1) abbia operazioni temporizzate su dict).

Tuttavia c'è un'alternativa a questi struttura di dati che dovrebbero fornire ottime prestazioni: blist. Il pacchetto blist fornisce un'interfaccia che corrisponde a quella di list, solo che viene implementata utilizzando alberi B. Può gestire in modo efficiente elenchi sparsi. La maggior parte delle operazioni richiede il tempo O(1) o O(log n), quindi sono abbastanza efficienti.

Per esempio si potrebbe creare prima una rada blist facendo:

from blist import blist 

seq = blist([None]) 
seq *= 2**30 # create a 2**30 element blist. Instantaneous! 

E allora si può impostare solo gli indici che hanno un valore:

for i, value in zip(indices, values): 
    seq[i] = value 

La documentazione completa è here.

noti che blist fornisce altre operazioni efficienti come:

  • Concatenating due blist s prendono O(log n) tempo
  • Prendendo un [i:j] fetta prende O(log n) tempo
  • Inserimento di un elemento in un determinato indice tiene O(log n) operazioni
  • Aprire un elemento (da ogni posizione) richiede operazioni O(log n)

Dal momento che ti ha dato alcuni numeri, ecco come si confronta con dict s:

>>> from blist import blist 
>>> b = blist([None]) 
>>> b *= 5000 
>>> for i in range(100):b[i] = i 
... 
>>> b.__sizeof__() 
2660 
>>> d = dict() 
>>> for i in range(100):d[i] = i 
... 
>>> d.__sizeof__() 
6216 
>>> b = blist([None]) 
>>> b *= 750 
>>> for i in range(30):b[i] = i 
... 
>>> b.__sizeof__() 
1580 
>>> d = dict() 
>>> for i in range(30):d[i] = i 
... 
>>> d.__sizeof__() 
1608 

In entrambi i casi un blist richiede meno memoria (nel primo caso ci vogliono 1/3 della memoria dell'equivalente dict). Si noti che la memoria presa da un blist dipende anche dal fatto che gli indici siano contigui o meno (contiguo è meglio). Tuttavia, anche a mezzo di indici casuali:

>>> b = blist([None]) 
>>> b *= 5000 
>>> import random 
>>> for i in range(100):b[random.randint(0, 4999)] = i 
... 
>>> b.__sizeof__() 
2916 

E 'ancora molto meglio del dict.

talvolta anche di ricerca sono migliori:

In [1]: from blist import blist 
    ...: import random 
    ...: 

In [2]: b = blist([None]) 

In [3]: b *= 5000 

In [4]: for i in range(100):b[random.randint(0, 4999)] = i 

In [5]: %timeit b[0] 
10000000 loops, best of 3: 50.7 ns per loop 

In [6]: d = dict() 

In [7]: for i in range(100):d[random.randint(0, 4999)] = i 

In [10]: %timeit d[1024] # 1024 is an existing key in this dictionary 
10000000 loops, best of 3: 70.7 ns per loop 

In [11]: %timeit b[1024] 
10000000 loops, best of 3: 50.7 ns per loop 

Nota che un list dura circa 47 ns per cercare l'indice sulla mia macchina, così blist è davvero molto vicino a un list in termini di prestazioni di ricerca su piccole liste come cos'hai.

+0

Quindi, il 'blist' sembra essere la risposta alla affermazione di @begueradj che è più veloce accedere a un' dict' di un 'elenco'. In che modo l'utilizzo della memoria di un 'blist' sparsa si confronta con un' dict'? Sembra un 95% vuoto (dai numeri che ho aggiunto alla domanda) 'blist' userà ancora più memoria di un' dict' (anche se non capisco come funziona "mai più di 2/3 pieni"; un 'dict' occupa solo 1,5 volte la dimensione dei suoi dati?), e ancora più memoria se comincio eseguendo ciecamente un grande' blist' invece di calcolare prima il max id, o fa 'blist' collassare gli indici vuoti in qualche modo? – Rus925

+1

@ Rus925 Un blist sparse usa la memoria 'O (log n)' dove 'n' sarebbe la lunghezza dell'elenco reale. Ciò significa che un blist sparse '2 ** 30' (come nell'esempio) occupa circa' k * 30' byte, dove 'k' è una costante. La memoria utilizzata dipende da quanto sparsa è la lista e dove sono gli elementi (se sono raggruppati sarà più efficiente). Come mostra l'esempio, non è necessario memorizzare tutti gli elementi effettivi (non è possibile memorizzare una lista di elementi '2 ** 30' nella RAM). Ovviamente se sai quanti oggetti hai puoi usare quel numero invece di un grande valore casuale. – Bakuriu

+1

@ Rus925 Ho aggiornato la mia risposta.AFAIK, dai miei test un blist vuoto al 95% richiede * meno * memoria di un 'dict 'ed è anche più veloce da cercare. – Bakuriu

1

Liste:
1. append e pop dalla fine della lista sono veloci
2. insert e pop dall'inizio di una lista sono lento (c'è un'operazione di merda pesante dietro queste 2 funzioni)
3. è preferibile utilizzare collection.degue per il secondo caso.

Dizionari:
4. Le operazioni di accesso sono più veloci rispetto alle liste



scorrendo dizionari e liste:

  1. Dizionari utilizzano iteritems() metodo per recuperare allo stesso tempo la chiave e il suo valore corrispondente.
  2. Gli elenchi utilizzano enumerate() per lo stesso scopo.

    Note:
  3. Se la tua domanda è solo di loop di velocità, non c'è differenza tangibile tra iteritems() ed enumerare()
  4. iteritems() è removed in Python 3.x.
  5. Il metodo zip() è un processo pesante da evitare.
1

Penso che non ci sia una risposta generale a questa domanda. Dipende dalla ripartizione dell'identificatore di interi, dalla memoria disponibile e dai requisiti di prestazione. Le regole sono:

  • un elenco di ricerca è più veloce, perché non è necessario calcolare l'hash della chiave.
  • un dict può essere più compatto se il valore più grande della chiave è di grandi dimensioni
  • se la tua chiave più grande è molto grande (circa 2^30) si rifiuti di un sacco di memoria e il sistema inizierà swap che degrada notevolmente le prestazioni

Ecco quello che potrebbe essere una regola empirica:

  • se ci sono "alcuni" valori vuoti, se si conosce la chiave più grande sarà "ragionevolmente" basso (rispetto alla memoria si accetta di spendere per quello) => utilizzare una lista
  • se il seguente requisito non viene verificato e non si dispone di forte requisito di prestazione => utilizzare un dict
  • se nessuna delle 2 ipotesi precedenti sono vere si dovrà provare alcune funzioni di hash ottimizzazioni - I dettagli qui sotto

La teoria del dict è una matrice per cui l'indice è il risultato di una funzione di hash applicata alla chiave. L'algoritmo di Python è correttamente ottimizzato ma è generalista. Se sai di avere una ripartizione speciale, potresti provare a trovare un hash appositamente adattato a la tua ripartizione. Si potrebbero trovare indicazioni per andare oltre nell'articolo di Wikipedia su Hash functions o sulla buona vecchia libreria standard C hash

Problemi correlati