日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
為了1%情形,犧牲99%情形下的性能:蝸牛般的Python深拷貝

最近使用 Python 一個(gè)項(xiàng)目,發(fā)現(xiàn) Python 的深拷貝 copy.deepcopy 實(shí)在是太慢了。

創(chuàng)新互聯(lián)建站提供網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì),成都品牌網(wǎng)站建設(shè),廣告投放等致力于企業(yè)網(wǎng)站建設(shè)與公司網(wǎng)站制作,十載的網(wǎng)站開(kāi)發(fā)和建站經(jīng)驗(yàn),助力企業(yè)信息化建設(shè),成功案例突破上1000+,是您實(shí)現(xiàn)網(wǎng)站建設(shè)的好選擇.

相關(guān)背景

在 Python 中, 我們有兩種拷貝對(duì)象的方式:淺拷貝和深拷貝。淺拷貝和深拷貝都可以在 copy 模塊中找到, 其中 copy.copy() 進(jìn)行淺拷貝操作, copy.deepcopy() 進(jìn)行深拷貝操作。淺拷貝是在另一塊地址中創(chuàng)建一個(gè)新的對(duì)象,但是新對(duì)象內(nèi)的子對(duì)象引用指向源對(duì)象的子對(duì)象。如果這時(shí)候我們修改源對(duì)象的子對(duì)象的屬性, 新對(duì)象中子對(duì)象的屬性也會(huì)改變。為了獲取一個(gè)和源對(duì)象沒(méi)有任何關(guān)系的全新對(duì)象,我們需要深拷貝。深拷貝是在另一塊地址中創(chuàng)建一個(gè)新的對(duì)象,同時(shí)對(duì)象內(nèi)的子對(duì)象也是新開(kāi)辟的,和源對(duì)象對(duì)比僅僅是值相同。

下面用一個(gè)例子說(shuō)明淺拷貝和深拷貝的區(qū)別,下面的代碼將輸出 "b [2,2,3]" 和 "c [1,2,3]"。

 
 
 
 
  1. import copyclass A: 
  2.    def __init__(self): 
  3.     array = [1,2,3] 
  4. a = A() 
  5. b = copy.copy(a) 
  6. c = copy.deepcopy(a) 
  7. a.array[0] = 2 
  8. print "b",b.array 
  9. print "c",c.array 

b 是由 a 淺拷貝而來(lái),c 是由 a 深拷貝而來(lái)。修改 a.array 之后, b.array 也隨之發(fā)生變化。其實(shí) a.array 和 b.array 指向同一個(gè)對(duì)象。而 c.array 則保持原樣。

深拷貝比淺拷貝符合人類(lèi)的直覺(jué),代價(jià)就是深拷貝實(shí)在是太慢了。下面代碼中,case1 和 case2 是等價(jià)的。在我的機(jī)器上測(cè)試,case1 不到一秒,而 case2 則達(dá)到了 十秒。那么深拷貝為什么那么慢呢?

 
 
 
 
  1. import copyclass A: 
  2.    def __init__(self): 
  3.        array = [1,2,3] 
  4.  
  5. a = [A() for i in xrange(100000)] 
  6. a[10].array[0] = 100 
  7.  
  8. ### case1 
  9. b = [A() for i in xrange(100000)] 
  10. for i in xrange(100000): 
  11.     for j in xrange(3): 
  12.        b[i].array[j] = a[i].array[j] 
  13.  
  14. ### case2 
  15. c = copy.deepcopy(a) 

深拷貝分析

為了搞清楚為什么 Python 深拷貝這么慢,我閱讀了下 Python 2.7 版本的 copy.deepcopy 的實(shí)現(xiàn): https://github.com/python/cpython/blob/2.7/Lib/copy.py。其大體結(jié)構(gòu)為:

 
 
 
 
  1. def deepcopy(x, memo=None, _nil=[]): 
  2.     if memo is None: 
  3.         memo = {} 
  4.     d = id(x) 
  5.     y = memo.get(d, _nil) 
  6.     if y is not _nil: 
  7.         return y 
  8.   
  9.     cls = type(x) 
  10.   
  11.     copier = _deepcopy_dispatch.get(cls) 
  12.     if copier: 
  13.         y = copier(x, memo) 
  14.     else: 
  15.         ... ## 其他特殊的拷貝方式 
  16.  
  17.     memo[d] = y 
  18.     return y 

其中 memo 保存著所有拷貝過(guò)的對(duì)象。為什么要設(shè)置 memo 呢?在某些特殊情況下,一個(gè)對(duì)象的相關(guān)對(duì)象可以指向它自己,比如雙向鏈表。如果不將拷貝過(guò)的對(duì)象存著,那程序?qū)⑾萑胨姥h(huán)。另外一個(gè)值得注意的點(diǎn)是 _deepcopy_dispatch,這行代碼根據(jù)待拷貝對(duì)象的類(lèi)型,選擇相應(yīng)的拷貝函數(shù)??蛇x的拷貝函數(shù)有:用于拷貝基本數(shù)據(jù)類(lèi)型的 _deepcopy_atomic, 用于拷貝列表的 _deepcopy_list,用于拷貝元組 _deepcopy_tuple,用于拷貝字典 的 _deepcopy_dict,用于拷貝自定義對(duì)象的 _deepcopy_inst 等等。其中比較重要的拷貝函數(shù) __deepcopy_inst 代碼如下所示:如果對(duì)象有 _ _ deepcopy _ _ 函數(shù),則使用該函數(shù)進(jìn)行拷貝;如果沒(méi)有,那么先拷貝初始構(gòu)造參數(shù),然后構(gòu)造一個(gè)對(duì)象,再拷貝對(duì)象狀態(tài)。

 
 
 
 
  1. def _deepcopy_inst(x, memo): 
  2.     if hasattr(x,'__deepcopy__'): 
  3.         return x.__deepcopy__(memo) 
  4.     if hasattr(x,'__getinitargs__'): 
  5.         args = x.__getinitargs__() 
  6.         args = deepcopy(args, memo) 
  7.         y = x.__class__(*args) 
  8.     else: 
  9.         y = _EmptyClass() 
  10.         y.__class__ = x.__class__ 
  11.  
  12.     memo[id(x)] = y     
  13.     if hasattr(x, '__getstate__'): 
  14.         state = x.__getstate__() 
  15.     else: 
  16.         state = x.__dict__ 
  17.     state = deepcopy(state, memo) 
  18.     if hasattr(y, '__setstate__'): 
  19.         y.__setstate__(state) 
  20.     else: 
  21.         y.__dict__.update(state) 
  22.  
  23.     return y 

深拷貝需要維護(hù)一個(gè) memo 用于記錄已經(jīng)拷貝的對(duì)象,這是它比較慢的原因。在絕大多數(shù)情況下,程序里都不存在相互引用。但作為一個(gè)通用的模塊,Python 深拷貝必須為了這 1% 情形,犧牲 99% 情形下的性能。

一個(gè)場(chǎng)景

上面給出了深拷貝效率對(duì)比的結(jié)果,已經(jīng)可以看出深拷貝很慢了,但沒(méi)有辦法直觀感受深拷貝拖累整個(gè)程序的運(yùn)行速度。下面給一個(gè)實(shí)際項(xiàng)目的例子,最近在寫(xiě)的一些游戲環(huán)境。玩家程序獲得游戲環(huán)境給出的信息,當(dāng)前玩家程序選擇合適的動(dòng)作,游戲環(huán)境根據(jù)該動(dòng)作推進(jìn)游戲邏輯;重復(fù)上述過(guò)程,直到分出勝負(fù);整個(gè)過(guò)程如下所示。給玩家程序的信息必須從游戲環(huán)境深拷貝出來(lái)。如果給玩家的信息是從游戲環(huán)境淺拷貝出來(lái)的,那么玩家程序有可能通過(guò)信息獲取游戲秘密或者操縱游戲。

我們已經(jīng)知道默認(rèn)的深拷貝很慢。為了改進(jìn)深拷貝的效率,我們?cè)谛畔㈩?lèi)以及相關(guān)類(lèi)都添加了 _ _ deepcopy _ _ 函數(shù),下面是信息類(lèi)示例。

 
 
 
 
  1. class FiveCardStudInfo(roomai.abstract.AbstractInfo): 
  2.     public_state  = None 
  3.     person_state  = None 
  4.  
  5.     def __deepcopy__(self, memodict={}): 
  6.         info = FiveCardStudInfo() 
  7.         info.public_state = self.public_state.__deepcopy__() 
  8.         info.public_state = self.person_state.__deepcopy__() 
  9.         return info 

簡(jiǎn)單做了一個(gè)效率對(duì)比實(shí)驗(yàn):讓隨機(jī)玩法玩家打一千局梭哈,統(tǒng)計(jì)耗時(shí)。實(shí)驗(yàn)結(jié)果如下所示。

使用原始深拷貝,程序運(yùn)行時(shí)間為 143 秒,其中深拷貝時(shí)間 134 秒,深拷貝時(shí)間占整個(gè)程序時(shí)間的 94%。使用了改進(jìn)深拷貝,程序運(yùn)行時(shí)間是 23 秒,其中深拷貝時(shí)間 16 秒,占比為 69 %。雖然深拷貝依然占到了 69%,相比之間 94 % 已經(jīng)下降很多。整個(gè)程序運(yùn)行時(shí)間也從 134 秒減少到 23 秒了。

總結(jié)

Python 的深拷貝很慢,原因在于深拷貝需要維護(hù)一個(gè) memo 用于記錄已經(jīng)拷貝的對(duì)象。而維護(hù)這個(gè) memo 的原因是為了避免相互引用造成的死循環(huán)。但作為一個(gè)通用的模塊,Python 深拷貝必須為了這 1% 情形,犧牲 99% 情形下的性能。我們可以通過(guò)自己實(shí)現(xiàn) _ _ deepcopy _ _ 函數(shù)來(lái)改進(jìn)其效率。

【本文為專(zhuān)欄作者“李立”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)獲取聯(lián)系和授權(quán)】


分享名稱(chēng):為了1%情形,犧牲99%情形下的性能:蝸牛般的Python深拷貝
本文網(wǎng)址:http://www.5511xx.com/article/dpjgiic.html