2010-11-16 9 views
12

Abbiamo un server Web basato su Python che designa un numero di file di dati di grandi dimensioni all'avvio usando cPickle. I file di dati (decapitati usando HIGHEST_PROTOCOL) sono circa 0,4 GB su disco e vengono caricati in memoria come circa 1,2 GB di oggetti Python - questo richiede circa 20 secondi. Stiamo usando Python 2.6 su macchine Windows a 64 bit.Come deserializzare 1GB di oggetti in Python più velocemente di cPickle?

Il collo di bottiglia non è certamente il disco (ci vogliono meno di 0,5 secondi per leggere effettivamente tanti dati), ma l'allocazione della memoria e la creazione dell'oggetto (ci sono milioni di oggetti creati). Vogliamo ridurre gli anni '20 per ridurre i tempi di avvio.

C'è un modo per deserializzare più di 1 GB di oggetti in Python molto più veloce di cPickle (come 5-10x)? Poiché il tempo di esecuzione è legato all'allocazione della memoria e alla creazione dell'oggetto, presumo che l'uso di un'altra tecnica di disaccoppiamento come JSON non sarebbe di aiuto in questo caso.

So che alcuni linguaggi interpretati hanno un modo per salvare l'intera immagine di memoria come un file su disco, in modo che possano caricarli nuovamente in memoria tutto in una volta, senza allocazione/creazione per ciascun oggetto. C'è un modo per fare questo, o raggiungere qualcosa di simile, in Python?

+1

Questa potrebbe essere la tua occasione per ottenere un'unità a stato solido. È questo per accelerare dev? Per consentirti di effettuare implementazioni rapide? Il ritardo nella lettura dei dati o nella deselezione? Se inizi con un'istanza vuota, qual è l'ora di avvio? – Scott

+0

Nota che nella mia domanda ho menzionato che il collo di bottiglia non è la velocità di avanzamento/lettura, ma la velocità di estrazione e di creazione dell'oggetto. È più per le implementazioni rapide - per consentire al nostro server di riavviare rapidamente. Non sono abbastanza sicuro di cosa intendi con "istanza vuota" qui. –

+0

Per un file binario di pickle da 750 MB, l'impostazione della chiamata di carico cPickle con gc.disable()/gc.enable() ha ridotto drasticamente il tempo totale richiesto di circa 20 volte. Vedi [qui] (http://stackoverflow.com/a/36699998/2385420) –

risposta

16
  1. Provare il modulo marshal: è interno (utilizzato dal byte-compilatore) e intenzionalmente non viene pubblicizzato molto, ma è molto più veloce. Si noti che non serializza istanze arbitrarie come pickle, solo i tipi built-in (non ricordare i vincoli esatti, vedere i documenti). Si noti inoltre che il formato non è stabile.

  2. Se è necessario inizializzare più processi e può tollerare un processo sempre caricato, esiste una soluzione elegante: caricare gli oggetti in un unico processo e quindi non fare nulla al suo interno, tranne eseguire i processi su richiesta. Il forking è veloce (copia su scrittura) e condivide la memoria tra tutti i processi. [Disclaimer: non testato; unlike Ruby, Python conteggio ref si attivano le copie della pagina quindi questo è probabilmente inutile se si dispone di enormi oggetti e/o di accedere a una piccola frazione di essi.]

  3. Se gli oggetti contengono un sacco di dati grezzi come array numpy, è può memorizzarli in memoria per un avvio molto più veloce. pytables è anche un bene per questi scenari.

  4. Se si utilizzerà solo una piccola parte degli oggetti, è probabile che un database OO (come Zope) possa essere di aiuto. Anche se ne hai bisogno tutti in memoria, ti basterà perdere un sacco di spese generali per poco guadagno. (mai usato, quindi potrebbe essere una sciocchezza).

  5. Forse altre implementazioni Python possono farlo? Non lo so, solo un pensiero ...

+0

Grazie, roba utile. Per tua informazione, nel mio test rapido su un file di grandi dimensioni con molti oggetti, marshal.loads() era circa il doppio di pickle.loads(). –

+0

La stessa esperienza qui su un enorme dizionario; marshal.load richiede solo 0,78 s, dove cPickle.load richiede 1,2 s. – unhammer

+0

L'opzione numero due sarebbe problematica, poiché gli oggetti verranno copiati per ogni sottoprocesso biforcuto nel momento in cui si fa riferimento a questi oggetti. Questo perché ogni oggetto ha un conteggio ref che cambia ogni volta che si accede all'oggetto. Questo a sua volta è come cambiare l'oggetto e quindi fa sì che la memoria venga copiata. Essenzialmente la copia su write diventa una copia all'accesso quando python è interessato ... – FableBlaze

3

Non ho usato cPickle (o Python) ma in casi come questo penso che la strategia migliore sia quella di evitare il caricamento non necessario degli oggetti fino a quando non sono realmente necessari - diciamo carico dopo l'avvio su un thread diverso, in realtà di solito è meglio evitare inutili operazioni di caricamento/inizializzazione in qualsiasi momento per ovvi motivi. Google "lazy loading" o "lazy initialization". Se hai davvero bisogno che tutti gli oggetti eseguano qualche compito prima dell'avvio del server, allora potresti provare ad implementare un metodo di deserializzazione manuale manuale, in altre parole implementare qualcosa da te se hai una conoscenza approfondita dei dati che ti occuperai e che possono aiutarti 'spremere' le prestazioni migliori quindi lo strumento generale per gestirle.

3

Hai provato a sacrificare l'efficienza del decapaggio non utilizzando HIGHEST_PROTOCOL? Non è chiaro quali siano i costi delle prestazioni associati all'uso di questo protocollo, ma potrebbe valere la pena provare.

+0

Buon pensiero. Tuttavia, inizialmente stavamo usando il protocollo predefinito (più basso), ma il passaggio a HIGHEST_PROTOCOL (un protocollo basato su binario) lo ha accelerato di un fattore due. Quindi HIGHEST_PROTOCOL è decisamente più veloce. –

2

Impossibile rispondere a questo senza sapere di più sul tipo di dati che si sta caricando e su come lo si utilizza.

Se si tratta di una sorta di logica aziendale, forse dovresti provare a trasformarlo in un modulo precompilato;

Se si tratta di dati strutturati, è possibile delegarli a un database e solo tirare ciò che è necessario?

I dati hanno una struttura regolare? C'è un modo per dividerlo e decidere cosa è richiesto e solo dopo caricarlo?

7

Si carica() i dati prelevati direttamente dal file? Che ne dici di provare a caricare il file nella memoria e poi a caricare? Vorrei iniziare provando il cStringIO(); in alternativa, puoi provare a scrivere la tua versione di StringIO che userebbe buffer() per tagliare la memoria che ridurrebbe le operazioni copy() necessarie (cStringIO potrebbe essere ancora più veloce, ma dovrai provare).

A volte ci sono enormi colli di bottiglia nelle prestazioni quando si eseguono questi tipi di operazioni, specialmente sulla piattaforma Windows; il sistema Windows è in qualche modo molto poco ottimizzato per fare molte piccole letture mentre UNIX riesce a far fronte abbastanza bene; se load() fa molte letture piccole o stai chiamando load() più volte per leggere i dati, questo sarebbe di aiuto.

+1

Alla persona che mi ha dato il -1: prova a caricare un file chiamando read (1) su Win; quindi prova a farlo su Unix. Ci vogliono diversi secondi per leggere alcuni megabyte su Windows; è ancora istantaneo su Unix. Se benhoyt carica un sacco di oggetti chiamando diverse decine di migliaia di chiamate pickle.load() da un file, questo potrebbe essere un fattore. – ondra

+0

Buona chiamata. Sui nostri dati, cambiando "obj = pickle.load (f)" in "s = f.read(); obj = pickle.loads (s)" si ottiene un aumento della velocità del 30%. Non ordini di grandezza, ma vale la pena saperlo. (A proposito, ho accidentalmente premuto verso il basso anziché verso l'alto, sentitevi libero di apportare una piccola modifica alla vostra risposta in modo da poterlo sovvertire.) –

+0

Ho trovato che ci sia un notevole miglioramento per fare lo stesso processo con il modulo maresciallo (che rende senso poiché è un problema di Windows). –

2

io aggiungo un'altra risposta che potrebbe essere utile - se è possibile, si può provare a definire _ slot _ sulla classe che è più comunemente creato? Questo può essere un po 'limitante e impossibile, tuttavia sembra che abbia ridotto a circa la metà il tempo necessario per l'inizializzazione del mio test.

Problemi correlati