2009-09-11 51 views
28

我有一個小部件的樹形結構,例如集合包含模型和模型包含小部件。我想複製整個集合,copy.deepcopy相比更快,以「泡菜和去pickle'ing的對象,而是作爲cPickle的被用C寫的要快得多,所以copy.deepcopy vs pickle

  1. 爲什麼我不應該(我們)總是使用cPickle而不是deepcopy?
  2. 是否有其他複製替代方案?因爲泡菜是慢然後deepcopy的,但是cPickle的速度更快,所以可能是一個C實現deepcopy的將是贏家

樣品測試代碼:

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 
+2

這是一個非常有用的觀察。 – bayer 2009-09-11 12:56:07

+0

hm,難道你不應該將'pickle'與'copy.copy'比較嗎? – SilentGhost 2009-09-11 12:59:39

+0

它是否完全依賴於你正在複製的結構?如在中,如果分配給複製對象的內存不連續或者底層指針有很長的鏈要遵循,它是否會產生影響? – 2009-09-11 13:24:23

回答

29

問題是,pickle + unpickle可以更快(在C實現中),因爲它是不是一般的比deepcopy:許多對象可以被深度複製但不被醃製。假設,例如,你的A類改爲...:

class A(object): 
    class B(object): pass 
    def __init__(self): self.b = self.B() 

現在,copy1仍然能正常工作(A的複雜性延緩它起伏,但絕對不停止的話); copy2copy3突破,堆棧跟蹤說的最後...:

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 

也就是說,總是酸洗假定類和函數是頂層實體及其模塊,所以泡菜他們「按名稱」 - - 深拷貝絕對沒有這樣的假設。因此,如果您遇到「稍微深度複製」的速度至關重要的情況,則每毫秒都很重要,並且您希望利用您已知道的適用於正在複製的對象的特殊限制,例如那些使酸洗適用的,或者其他形式的序列化和其他快捷方式的人都會繼續 - 但是如果你這樣做,你必須意識到你正在制約你的系統永遠依賴這些限制,並且記錄下這個設計爲了未來的維護者的利益,非常清楚明確地做出決定。

對於正常情況下,如果你想要的通用性,使用deepcopy - !)

5

您應該使用deepcopy,因爲它使您的代碼更具可讀性。使用序列化機制在內存中複製對象至少會讓另一位閱讀代碼的開發人員感到困惑。使用深度複製也意味着您可以從深度複製中獲得未來優化的好處。

優化的第一條規則:不要。

+6

優化的第二條規則:暫時還沒有 – voyager 2009-09-11 14:21:47

+2

忘記過時的規則,用cpickle替換deepcopy,使我的項目渲染速度提高25%,讓我的客戶開心:) – 2009-09-11 14:31:11

+1

@wds,如果包裝函數複製對象被命名爲deepcopyObject,並且很好的評論 – 2009-09-11 14:31:48

1

更快速的是避免在首位的副本。你提到你在做渲染。爲什麼需要複製對象?

+1

是理想情況下它不需要複製,因爲視圖(渲染)和模型將被分離,但在我的情況下,渲染確實會修改模型,因此我需要在渲染之前複製模型,所以原始不會被修改 – 2009-09-11 15:04:53

+1

我不會打死一匹死馬並不意味着,但修復渲染修改模型的問題會讓你非常開心。 – 2009-09-11 17:12:34

+0

我同意,但這將是一個代價昂貴的事情,改變這麼多的代碼,因爲這裏的討論是不可能的我已經添加了一個問題 http://stackoverflow.com/questions/1414246/how-to-decouple-model-view- for-widgets – 2009-09-12 04:08:15

0

短而有些晚:

  • 如果你無論如何都要cPickle的一個對象,你還不如用cPickle的方法deepcopy的(但文件)

例如你可能會考慮:

def mydeepcopy(obj): 
    try: 
     return cPickle.loads(cPickle.dumps(obj, -1)) 
    except PicklingError: 
     return deepcopy(obj) 
+0

爲什麼我的新行會消失,stov中的錯誤? – Lars 2013-09-28 10:01:29

+0

但這不是問題,我已經以類似的方式使用cPickle – 2013-09-28 18:51:24

+1

除了編寫自己的python c擴展 – Lars 2013-09-29 19:48:37

1

這是總是cPickle的比deepcopy的速度更快的情況下()。雖然cPickle的大概總是比鹹菜快,無論是快於deepcopy的取決於

  • 的規模和結構的嵌套層次被複制,
  • 包含的對象的類型,並
  • 大小醃製的字符串表示。

如果能找到一些醃製,也可以明顯地deepcopied,但相反的情況並非如此:爲了醃製的東西,它需要全系列化;這不是深拷貝的情況。特別是,您可以通過複製內存中的結構(考慮擴展類型),而無需將所有內容保存到磁盤,從而非常高效地實現__deepcopy__。 (考慮暫停到內存與暫停到磁盤)

滿足上述條件的着名擴展類型可能是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__不落實,copypickle有着共同的基礎設施(參見copy_reg模塊,在Relationship between pickle and deepcopy討論)。