copy.deepcopy vs pickle
質問
ウィジェットのツリー構造があります。コレクションにはモデルが含まれ、モデルにはウィジェットが含まれます。コレクション全体をコピーしたいのですが、 copy.deepcopy
はオブジェクトの「ピクルとデピクル」に比べて高速ですが、Cで記述されているcPickleははるかに高速です。
- なぜディープコピーではなくcPickleを常に使用すべきではないのですか?
- 他のコピーの代替手段はありますか? pickleはdeepcopyよりも遅いがcPickleは高速であるため、deepcopyのC実装が勝者になる可能性があります
サンプルテストコード:
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))
タイミング:
>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
解決
問題は、pickle + unpickleは、ディープコピーよりも一般的ではないため、(C実装では)高速になる可能性があることです。多くのオブジェクトはディープコピーできますが、ピクルできません。たとえば、クラス A
が...:
class A(object):
class B(object): pass
def __init__(self): self.b = self.B()
今、 copy1
は引き続き正常に動作します(Aの複雑さは速度を低下させますが、絶対に停止しません)。 copy2
と copy3
が壊れ、スタックトレースの終わりに...:
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
つまり、pickleは常にクラスと関数がモジュール内のトップレベルのエンティティであると想定しているため、「名前で」それらをpickleします。 -ディープコピーでは、このような仮定は一切行われません。
「やや深いコピー」の速度が速い場合ミリ秒ごとが重要であり、ピクルスを適用できるようにするものや、他の形式のシリアル化や他のショートカットを好むものなど、複製するオブジェクトに適用する既知の特別な制限を活用したいすべてを先に進めますが、そうする場合は、システムがこれらの制限に永遠に耐えるように制約していることを認識し、将来のメンテナーの利益のためにその設計決定を非常に明確かつ明示的に文書化する必要があります。
一般性が必要な通常の場合は、 deepcopy
!-)
他のヒント
コードを読みやすくするため、deepcopyを使用する必要があります。シリアル化メカニズムを使用してメモリ内のオブジェクトをコピーすることは、少なくとも、コードを読んでいる別の開発者にとって混乱を招きます。ディープコピーを使用すると、ディープコピーで将来の最適化の利点を享受することもできます。
最適化の最初のルール:しないでください。
cPickleがdeepcopy()よりも高速であるのは、常に ではありません。 cPickleはおそらくpickleよりも常に高速ですが、deepcopyよりも高速かどうかは依存します
- コピーする構造のサイズと入れ子レベル
- 含まれるオブジェクトのタイプ、および
- ピクルスされた文字列表現のサイズ。
何かをピクルできる場合、それは明らかにディープコピーできますが、逆はそうではありません。ピクルするためには、完全にシリアル化する必要があります。これはディープコピーの場合ではありません。特に、すべてをディスクに保存することなく、メモリ内の構造(拡張タイプを考えてください)をコピーすることにより、 __ deepcopy __
を非常に効率的に実装できます。 (RAMへのサスペンドとディスクへのサスペンドを考えてください。)
上記の条件を満たす有名な拡張タイプは ndarray
である可能性があり、実際、これはあなたの観察に対する良い反例として機能します:With d = numpy.arange(100000000)
、コードは異なるランタイムを提供します:
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
__ deepcopy __
が実装されていない場合、 copy
と pickle
は共通のインフラストラクチャを共有します(参照: copy_reg
モジュール、 ピクルとディープコピーの関係)。
最初の段階でコピーを避けることでさえ、より高速になります。あなたはレンダリングをしていると言います。オブジェクトをコピーする必要があるのはなぜですか?
短くてやや遅い:
- オブジェクトをcPickleする必要がある場合は、cPickleメソッドを使用してディープコピー(ただしドキュメント)することもできます
e.g。あなたが考慮するかもしれない:
def mydeepcopy(obj):
try:
return cPickle.loads(cPickle.dumps(obj, -1))
except PicklingError:
return deepcopy(obj)