copia.deepcopy vs sottaceto
Domanda
Ho una struttura ad albero di widget, ad es. la raccolta contiene modelli e il modello contiene widget. Voglio copiare l'intera collezione, copy.deepcopy
è più veloce rispetto a "decapare e decapare" l'oggetto ma cPickle come scritto in C è molto più veloce, quindi
- Perché non dovremmo sempre usare cPickle invece di deepcopy?
- Esistono altre alternative alla copia? perché il sottaceto è più lento del deepcopy ma cPickle è più veloce, quindi potrebbe essere un'implementazione in C del deepcopy che sarà il vincitore
Codice di prova di esempio:
import copy
import pickle
import cPickle
class A(object): pass
d = {}
for i in range(1000):
d[i] = A()
def copy1():
return copy.deepcopy(d)
def copy2():
return pickle.loads(pickle.dumps(d, -1))
def copy3():
return cPickle.loads(cPickle.dumps(d, -1))
Tempi:
>python -m timeit -s "import c" "c.copy1()"
10 loops, best of 3: 46.3 msec per loop
>python -m timeit -s "import c" "c.copy2()"
10 loops, best of 3: 93.3 msec per loop
>python -m timeit -s "import c" "c.copy3()"
100 loops, best of 3: 17.1 msec per loop
Soluzione
Il problema è che pickle + unpickle può essere più veloce (nell'implementazione in C) perché è meno generale rispetto al deepcopy: molti oggetti possono essere copiati in profondità ma non decapati. Supponiamo ad esempio che la tua classe A
sia stata modificata in ...:
class A(object):
class B(object): pass
def __init__(self): self.b = self.B()
ora copy1
funziona ancora bene (la complessità di A rallenta ma non la ferma assolutamente); copy2
e copy3
si interrompono, la fine della traccia dello stack dice ...:
File "./c.py", line 20, in copy3
return cPickle.loads(cPickle.dumps(d, -1))
PicklingError: Can't pickle <class 'c.B'>: attribute lookup c.B failed
Ad esempio, il decapaggio presuppone sempre che le classi e le funzioni siano entità di livello superiore nei loro moduli, e quindi le decapita "per nome". - il deepcopying non fa assolutamente simili assunzioni.
Quindi se hai una situazione in cui la velocità di "un po 'di copia profonda" è assolutamente cruciale, ogni millesimo di secondo è importante, e vuoi approfittare delle speciali limitazioni che SAPI applicare agli oggetti che stai duplicando, come quelli che rendono applicabile il decapaggio, o quelli che favoriscono altre forme ma di serializzazioni e altre scorciatoie, da tutti i mezzi vanno avanti, ma se lo fai DEVI essere consapevole del fatto che stai vincolando il tuo sistema a rispettare tali limiti per sempre, e documenta la decisione di progettazione in modo molto chiaro ed esplicito a beneficio dei futuri manutentori.
Per il caso NORMAL, dove si desidera la generalità, utilizzare deepcopy
! -)
Altri suggerimenti
Dovresti usare deepcopy perché rende il tuo codice più leggibile. L'uso di un meccanismo di serializzazione per copiare oggetti in memoria è per lo meno fonte di confusione per un altro sviluppatore che legge il tuo codice. L'uso di deepcopy significa anche ottenere i benefici delle future ottimizzazioni in deepcopy.
Prima regola di ottimizzazione: no.
non è sempre il caso che cPickle sia più veloce di deepcopy (). Mentre cPickle è probabilmente sempre più veloce di sottaceto, se è più veloce della deepcopy dipende da
- le dimensioni e il livello di annidamento delle strutture da copiare,
- il tipo di oggetti contenuti e
- la dimensione della rappresentazione in stringa decapata.
Se qualcosa può essere decapato, può ovviamente essere copiato in profondità, ma non è il contrario: Per decapare qualcosa, deve essere completamente serializzato ; questo non è il caso della copia in profondità. In particolare, puoi implementare __deepcopy__
in modo molto efficiente copiando una struttura in memoria (pensa ai tipi di estensione), senza poter salvare tutto sul disco. (Pensa a suspend-to-RAM vs. suspend-to-disk.)
Un tipo di estensione noto che soddisfa le condizioni di cui sopra può essere ndarray
e, in effetti, serve da valido controesempio alla tua osservazione: con d = numpy.arange (100000000)
, il tuo codice offre diversi runtime:
In [1]: import copy, pickle, cPickle, numpy
In [2]: d = numpy.arange(100000000)
In [3]: %timeit pickle.loads(pickle.dumps(d, -1))
1 loops, best of 3: 2.95 s per loop
In [4]: %timeit cPickle.loads(cPickle.dumps(d, -1))
1 loops, best of 3: 2.37 s per loop
In [5]: %timeit copy.deepcopy(d)
1 loops, best of 3: 459 ms per loop
Se __deepcopy__
non è implementato, copia
e pickle
condividono l'infrastruttura comune (cfr. modulo copy_reg
, discusso in Relazione tra sottaceto e deepcopy ).
Ancora più veloce sarebbe evitare la copia in primo luogo. Dici che stai facendo il rendering. Perché deve copiare oggetti?
Breve e un po 'in ritardo:
- Se devi comunque cPickle un oggetto, puoi anche usare il metodo cPickle per deepcopy (ma documento)
es. Potresti considerare:
def mydeepcopy(obj):
try:
return cPickle.loads(cPickle.dumps(obj, -1))
except PicklingError:
return deepcopy(obj)