国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Python - 優化Python代碼使其加快作用域內的查找

優化Python代碼使其加快作用域內的查找

2020-05-25 09:33James Saryerwinnie Python

這篇文章主要介紹了優化Python代碼使其加快作用域內的搜索,文中介紹了CPython相關的C代碼來對查找功能進行優化,加快搜索的速度,需要的朋友可以參考下

我將示范微優化(micro optimization)如何提升python代碼5%的執行速度。5%!同時也會觸怒任何維護你代碼的人。

但實際上,這篇文章只是解釋一下你偶爾會在標準庫或者其他人的代碼中碰到的代碼。我們先看一個標準庫的例子,collections.OrderedDict類:
 

?
1
2
3
4
5
6
def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
 if key not in self:
  root = self.__root
  last = root[0]
  last[1] = root[0] = self.__map[key] = [last, root, key]
 return dict_setitem(self, key, value)

注意最后一個參數:dict_setitem=dict.__setitem__。如果你仔細想就會感覺有道理。將值關聯到鍵上,你只需要給__setitem__傳遞三個參數:要設置的鍵,與鍵關聯的值,傳遞給內建dict類的__setitem__類方法。等會,好吧,也許最后一個參數沒什么意義。
作用域查詢

為了理解到底發生了什么,我們看下作用域。從一個簡單問題開始:在一個python函數中,如果遇到了一個名為open的東西,python如何找出open的值?
 

?
1
2
3
4
5
6
# <GLOBAL: bunch of code here>
 
def myfunc():
 # <LOCAL: bunch of code here>
 with open('foo.txt', 'w') as f:
  pass

簡單作答:如果不知道GLOBAL和LOCAL的內容,你不可能確定open的值。概念上,python查找名稱時會檢查3個命名空間(簡單起見忽略嵌套作用域):

    局部命名空間
    全局命名空間
    內建命名空間

所以在myfunc函數中,如果嘗試查找open的值時,我們首先會檢查本地命名空間,然后是全局命名空間,接著內建命名空間。如果在這3個命名空間中都找不到open的定義,就會引發NameError異常。
作用域查找的實現

上面的查找過程只是概念上的。這個查找過程的實現給予了我們探索實現的空間。
 

?
1
2
3
4
5
6
7
8
9
def foo():
 a = 1
 return a
 
def bar():
 return a
 
def baz(a=1):
 return a

我們看下每個函數的字節碼:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> import dis
>>> dis.dis(foo)
 2   0 LOAD_CONST    1 (1)
    3 STORE_FAST    0 (a)
 
 3   6 LOAD_FAST    0 (a)
    9 RETURN_VALUE
 
>>> dis.dis(bar)
 2   0 LOAD_GLOBAL    0 (a)
    3 RETURN_VALUE
 
>>> dis.dis(baz)
 2   0 LOAD_FAST    0 (a)
    3 RETURN_VALUE

注意foo和bar的區別。我們立即就可以看到,在字節碼層面,python已經判斷了什么是局部變量、什么不是,因為foo使用LOAD_FAST,而bar使用LOAD_GLOBAL。

我們不會具體闡述python的編譯器如何知道何時生成何種字節碼(也許那是另一篇文章的范疇了),但足以理解,python在執行函數時已經知道進行何種類型的查找。

另一個容易混淆的是,LOAD_GLOBAL既可以用于全局,也可以用于內建命名空間的查找。忽略嵌套作用域的問題,你可以認為這是“非局部的”。對應的C代碼大概是[1]:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
case LOAD_GLOBAL:
 v = PyObject_GetItem(f->f_globals, name);
 if (v == NULL) {
  v = PyObject_GetItem(f->f_builtins, name);
  if (v == NULL) {
   if (PyErr_ExceptionMatches(PyExc_KeyError))
    format_exc_check_arg(
       PyExc_NameError,
       NAME_ERROR_MSG, name);
   goto error;
  }
 }
 PUSH(v);

即使你從來沒有看過CPython的C代碼,上面的代碼已經相當直白了。首先,檢查我們查找的鍵名是否在f->f_globals(全局字典)中,然后檢查名稱是否在f->f_builtins(內建字典)中,最后,如果上面兩個位置都沒找到,就會拋出NameError異常。
將常量綁定到局部作用域

現在我們再看最開始的代碼例子,就會理解最后一個參數其實是將一個函數綁定到局部作用域中的一個函數上。具體是通過將dict.__setitem__賦值為參數的默認值。這里還有另一個例子:
 

?
1
2
3
4
5
def not_list_or_dict(value):
 return not (isinstance(value, dict) or isinstance(value, list))
 
def not_list_or_dict(value, _isinstance=isinstance, _dict=dict, _list=list):
 return not (_isinstance(value, _dict) or _isinstance(value, _list))

這里我們做同樣的事情,把本來將會在內建命名空間中的對象綁定到局部作用域中去。因此,python將會使用LOCAL_FAST而不是LOAD_GLOBAL(全局查找)。那么這到底有多快呢?我們做個簡單的測試:
 

?
1
2
3
4
$ python -m timeit -s 'def not_list_or_dict(value): return not (isinstance(value, dict) or isinstance(value, list))' 'not_list_or_dict(50)'
1000000 loops, best of 3: 0.48 usec per loop
$ python -m timeit -s 'def not_list_or_dict(value, _isinstance=isinstance, _dict=dict, _list=list): return not (_isinstance(value, _dict) or _isinstance(value, _list))' 'not_list_or_dict(50)'
1000000 loops, best of 3: 0.423 usec per loop

換句話說,大概有11.9%的提升 [2]。比我在文章開始處承諾的5%還多!
還有更多內涵

可以合理地認為,速度提升在于LOAD_FAST讀取局部作用域,而LOAD_GLOBAL在檢查內建作用域之前會先首先檢查全局作用域。上面那個示例函數中,isinstance、dict、list都位于內建命名空間。

但是,還有更多。我們不僅可以使用LOAD_FAST跳過多余的查找,它也是一種不同類型的查找。

上面C代碼片段給出了LOAD_GLOBAL的代碼,下面是LOAD_FAST的:
 

?
1
2
3
4
5
6
7
8
9
10
11
case LOAD_FAST:
 PyObject *value = fastlocal[oparg];
 if (value == NULL) {
  format_exc_check_arg(PyExc_UnboundLocalError,
        UNBOUNDLOCAL_ERROR_MSG,
        PyTuple_GetItem(co->co_varnames, oparg));
  goto error;
 }
 Py_INCREF(value);
 PUSH(value);
 FAST_DISPATCH()

我們通過索引一個數組獲取局部值。雖然沒有直接出現,但是oparg只是那個數組的一個索引。

現在聽起來才合理。我們第一個版本的not_list_or_dict要進行4個查詢,每個名稱都位于內建命名空間,它們只有在查找全局命名空間之后才會查詢。這就是8個字典鍵的查詢操作了。相比之下,not_list_or_dict的第二版中,直接索引C數組4次,底層全部使用LOAD_FAST。這就是為什么局部查詢更快的原因。
總結

現在當下次你在其他人代碼中看到這種例子,就會明白了。

最后,除非確實需要,請不要在具體應用中進行這類優化。而且大部分時間你都沒必要做。但是如果時候到了,你需要擠出最后一點性能,就需要搞懂這點。
腳注

[1]注意,為了更易讀,上面的代碼中我去掉了一些性能優化。真正的代碼稍微有點復雜。

[2]示例函數事實上沒有做什么有價值的東西,也沒進行IO操作,大部分是受python VM循環的限制。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 亚洲国产精品99久久久久久久久 | 求av网址| 亚洲一区二区三区高清 | 波多野结衣先锋影音 | 国产毛片网站 | 欧美在线观看免费观看视频 | 九九热精品在线 | а√天堂中文在线资源8 | 国产精品日韩欧美一区二区三区 | 日本成人一区 | 91精品在线看 | 国产一区二区影院 | 在线观看亚洲免费视频 | 激情在线观看视频 | 亚洲国产精品成人 | 亚洲av毛片一区二二区三三区 | 一级片av | 情一色一乱一欲一区二区 | 国产精品久久久久无码av | 国产99久久精品 | 亚洲国产精品成人 | 97久久久久久久久久久久 | 日韩精品一区二区在线观看 | 99久久婷婷国产精品综合 | 黄色美女网站视频 | 亚洲欧美中文字幕 | 色爱综合网 | 精品久久久久久 | 最好看的2019年中文在线观看 | 欧美精品在线一区二区 | 特级西西人体4444xxxx | 久久在线视频 | 亚洲国产精品一区久久av篠田 | 日韩中文字幕一区 | 亚洲一区视频在线 | 日韩和欧美的一区二区 | 久久男人网 | 久久亚洲国产精品日日av夜夜 | 久久丁香| 欧美天堂 | 午夜精品久久久久久久星辰影院 |