2010-10-26 12 views
8

ho bisogno di un efficiente della memoria int-int dict in Python che avrebbe sostenuto le seguenti operazioni in O (log n) tempo:efficiente della memoria int-int dict in Python

d[k] = v # replace if present 
v = d[k] # None or a negative number if not present 

ho bisogno di tenere ~ Coppie 250M, quindi è davvero deve essere stretto.

Ti capita di conoscere un'implementazione adatta (Python 2.7)?

EDIT Requisito impossibile rimosso e altre assurdità. Grazie, Craig e Kylotan!


di riformulare. Ecco un dizionario int-int banale con coppie di 1M:

>>> import random, sys 
>>> from guppy import hpy 
>>> h = hpy() 
>>> h.setrelheap() 
>>> d = {} 
>>> for _ in xrange(1000000): 
...  d[random.randint(0, sys.maxint)] = random.randint(0, sys.maxint) 
... 
>>> h.heap() 
Partition of a set of 1999530 objects. Total size = 49161112 bytes. 
Index Count %  Size % Cumulative % Kind (class/dict of class) 
    0  1 0 25165960 51 25165960 51 dict (no owner) 
    1 1999521 100 23994252 49 49160212 100 int 

In media, una coppia di interi utilizza 49 byte.

Ecco un array di interi 2M:

>>> import array, random, sys 
>>> from guppy import hpy 
>>> h = hpy() 
>>> h.setrelheap() 
>>> a = array.array('i') 
>>> for _ in xrange(2000000): 
...  a.append(random.randint(0, sys.maxint)) 
... 
>>> h.heap() 
Partition of a set of 14 objects. Total size = 8001108 bytes. 
Index Count %  Size % Cumulative % Kind (class/dict of class) 
    0  1 7 8000028 100 8000028 100 array.array 

In media, una coppia di numeri interi utilizza 8 byte.

Accetto che 8 byte/coppia in un dizionario sia piuttosto difficile da ottenere in generale. Domanda ripubblicata: esiste un'implementazione efficiente della memoria del dizionario int-int che utilizza molto meno di 49 byte/coppia?

+1

Forse I Non sto pensando in modo diretto, ma non vedo come la tua proposta di implementazione (con le chiavi in ​​corrispondenza di posizioni pari della matrice; valori in posizioni dispari) potrebbe essere * O (log n) * per entrambi gli inserimenti e le ricerche. –

+0

@Craig Oh, hai ragione. Nella mia implementazione non è possibile eseguire ricerche in _O (log n) _ (per chiavi diverse dalla più piccola). – Bolo

+0

In che modo le coppie 250M si riferiscono all'intervallo di valori-chiave? Ci sono 250 milioni di chiavi possibili e 250 milioni di coppie effettive, quindi la matrice è densa al 100%? – hughdbrown

risposta

6

Si potrebbe utilizzare i IIBtree da Zope

+0

Grazie, all'inizio non mi sono reso conto che 'IIBtree' sta usando" primitivo "(nel senso Java del termine) . È una struttura utile! Sfortunatamente costringe una dipendenza a Zope, che è piuttosto pesante. – Bolo

+0

Grazie! Ho un requisito simile e IIBTree è la struttura dati perfetta! Inoltre, ho installato BTrees-4.0.5 e ho inserito solo persistente-4.0.6 come dipendenza. – sayap

4

8 byte per chiave/valore pari sarebbe piuttosto difficile sotto qualsiasi implementazione, Python o in altro modo. Se non si ha la garanzia che le chiavi siano contigue allora si sprecerebbe molto spazio tra le chiavi usando una rappresentazione di matrice (oltre a richiedere una sorta di valore morto per indicare una chiave nulla), oppure Dovevo mantenere un indice separato per coppie chiave/valore che, per definizione, supererebbero gli 8 byte per coppia (anche se solo di una piccola quantità).

Ti suggerisco di utilizzare il metodo dell'array, ma l'approccio migliore dipenderà dalla natura dei tasti che mi aspetto.

+0

Grazie per il tuo contributo! Il mio trucco era dividere il dominio della chiave in una serie di matrici e mantenere le coppie chiave-valore ordinate all'interno di ogni matrice. In questo modo entrambe le letture (ricerca binaria) e scrittura (spostamento e inserimento) sono relativamente economiche. – Bolo

5

Non so se questa è una soluzione one-shot, o parte di un progetto in corso, ma se è la prima, sta lanciando più ram a meno del tempo di sviluppo necessario per ottimizzare l'utilizzo della memoria? Anche a 64 byte per coppia, stai ancora guardando solo 15 GB, che si adatteranno abbastanza facilmente nella maggior parte dei desktop box.

Penso che la risposta corretta risieda probabilmente nelle librerie SciPy/NumPy, ma non sono abbastanza familiare con la libreria per dirti esattamente dove cercare.

http://docs.scipy.org/doc/numpy/reference/

Si potrebbe anche trovare alcune idee utili in questa discussione: Memory Efficient Alternatives to Python Dictionaries

+0

Sono d'accordo. Penso anche che Numpy sia una delle soluzioni più probabili per gli array numerici efficienti (di memoria). – extraneon

+1

Grazie per il tuo contributo! È un compito ripetitivo, ed è fatto "dopo ore", con un budget di $ 0, su una scatola che non posso aggiornare. Inoltre, immagino sia meglio implementare una struttura dati riutilizzabile a memoria efficiente una volta che gettare soldi nell'hardware ogni volta che si esaurisce la memoria. Dalla mia esperienza limitata, SciPy/NumPy si concentra su numeri in virgola mobile, ed è di scarsa utilità qui. Il thread di riferimento è davvero interessante, grazie per la condivisione! – Bolo

+0

Avere una memoria int-int dict è una cosa generalmente utile che _should_ esiste. Dovrebbe essere una questione di googling o di chiedere a stackoverflow quale libreria sia più adatta. Questo dovrebbe essere molto più semplice dell'aggiornamento dell'hardware. – Ant6n

1

Ho implementato il mio dizionario Int-Int, available here (licenza BSD). In breve, io uso array.array('i') per archiviare le coppie chiave-valore ordinate per chiavi.Infatti, invece di un array di grandi dimensioni, tengo un dizionario di array più piccoli (una coppia chiave-valore è memorizzata nell'array key/65536) per accelerare lo spostamento durante l'inserimento e la ricerca binaria durante il recupero. Ogni array memorizza le chiavi ei valori nel modo seguente:

key0 value0 key1 value1 key2 value2 ... 

In realtà, non è solo un dizionario int-int, ma un dizionario oggetto int generale con oggetti ridotti ai loro hash. Pertanto, il dizionario hash-int può essere usato come una cache di un dizionario memorizzato in modo persistente.

Esistono tre possibili strategie di gestione delle "collisioni di chiavi", ovvero tentativi di assegnare un valore diverso alla stessa chiave. La strategia predefinita lo consente. La "cancellazione" rimuove la chiave e la contrassegna come collisione, in modo che qualsiasi ulteriore tentativo di assegnargli un valore non abbia alcun effetto. La strategia "urlando" genera un'eccezione durante qualsiasi tentativo di sovrascrittura e su qualsiasi ulteriore accesso a qualsiasi chiave in collisione.

Si prega di vedere my answer a a related question per una descrizione in modo diverso del mio approccio.

+0

È anche circa 10 volte più lento di un dettato standard, quando si inserisce un milione di numeri interi casuali usando un altro milione di numeri casuali come chiavi. E 20 volte più lento nel recupero. Sarebbe interessante sapere in quale caso va bene. :) –

+1

@Lennart Ogni paio di mesi ho bisogno di analizzare un grande grafico (link interwiki in Wikipedia): ~ 30M nodi, ognuno dei quali è una tupla e identificato da una stringa, probabilmente ~ 250M link, e in rapida crescita. Il grafico è memorizzato in un DB PostgreSQL, ma volevo un accesso significativamente più veloce, quindi ho provato a montare tutto il grafico nella RAM. A causa di collisioni di hash, di tanto in tanto devo colpire il DB, ma va bene. E il trade-off time-memory che hai menzionato è accettabile qui, dato che ottengo ancora i miei dati * molto * più velocemente di un DB. – Bolo

2

Guardando i tuoi dati sopra, non è 49 byte per int, è 25. Gli altri 24 byte per voce sono gli oggetti int stessi. Quindi è necessario qualcosa che sia significativamente più piccolo di byte per voce. A meno che tu non abbia intenzione di reimplementare gli oggetti int, che è possibile per gli hash della chiave, almeno. Oppure implementalo in C, dove puoi saltare completamente gli oggetti (questo è ciò che fa Zopes IIBTree, menzionato sopra).

Per essere onesti, il dizionario Python è ottimizzato in vari modi. Non sarà facile batterlo, ma buona fortuna.

+0

Grazie per la tua preziosa risposta. È vero, i 24 byte sono per i due oggetti int - ma con 'array 'puoi saltare gli oggetti e ridurli a 8 byte. Non sapevo che 'IIBTree' memorizzava anche intenti" primitivi ": né la documentazione né la risposta di gnibbler lo menzionavano. Grazie per questo chiarimento! Sebbene il dizionario Python sia davvero ben sintonizzato, ciò di cui ho bisogno è un'implementazione che sia massimizzata al massimo per lo spazio, a scapito del tempo (idealmente, per motivi di portabilità: scritta in Python, non C, e utilizzabile senza un grande esterno biblioteca come Zope). – Bolo

+1

@Bolo: È utilizzabile senza Zope, ma non senza ZODB, di cui fa parte. È molto più veloce, ma sembra utilizzare un po 'più di memoria della soluzione, da quello che posso raccogliere dei test (la stampa del mucchio guppy è inutile qui, probabilmente perché gli interi sono allocati in C). Ovviamente è fatto per memorizzare BTrees nello ZODB ed è anche usato dallo stesso ZODB. –

2

Che ne dici di un array Judy se stai mappando da ints? È una specie di array sparse ... Usa 1/4 dello spazio di implementazione del dizionario.

Judy:

$ cat j.py ; time python j.py 
import judy, random, sys 
from guppy import hpy 
random.seed(0) 
h = hpy() 
h.setrelheap() 
d = judy.JudyIntObjectMap() 
for _ in xrange(4000000): 
    d[random.randint(0, sys.maxint)] = random.randint(0, sys.maxint) 

print h.heap() 
Partition of a set of 4000004 objects. Total size = 96000624 bytes. 
Index Count %  Size % Cumulative % Kind (class/dict of class) 
    0 4000001 100 96000024 100 96000024 100 int 
    1  1 0  448 0 96000472 100 types.FrameType 
    2  1 0  88 0 96000560 100 __builtin__.weakref 
    3  1 0  64 0 96000624 100 __builtin__.PyJudyIntObjectMap 

real 1m9.231s 
user 1m8.248s 
sys  0m0.381s 

dizionario:

$ cat d.py ; time python d.py 
import random, sys 
from guppy import hpy 
random.seed(0) 
h = hpy() 
h.setrelheap() 
d = {} 
for _ in xrange(4000000): 
    d[random.randint(0, sys.maxint)] = random.randint(0, sys.maxint) 

print h.heap() 
Partition of a set of 8000003 objects. Total size = 393327344 bytes. 
Index Count %  Size % Cumulative % Kind (class/dict of class) 
    0  1 0 201326872 51 201326872 51 dict (no owner) 
    1 8000001 100 192000024 49 393326896 100 int 
    2  1 0  448 0 393327344 100 types.FrameType 

real 1m8.129s 
user 1m6.947s 
sys  0m0.559s 

~ 1/4 ° lo spazio:

$ echo 96000624/393327344 | bc -l 
.24407309958089260125 

(sto usando 64bit python, btw, così i miei numeri di base può essere gonfiato a causa di puntatori a 64 bit)

+0

Quale libreria stai usando per l'array judy? – fgregg

+0

Probabilmente questo: https://pypi.python.org/pypi/judy – rrauenza

Problemi correlati