[英]How to make Python generators as fast as possible?
對於編寫事件驅動的模擬器,我依賴於simpy ,它大量使用Python生成器。 我試圖了解如何盡可能快地生成生成器,即最小化狀態保存/恢復開銷。 我嘗試了三種選擇
並使用Python 3.4.3獲得以下結果:
class_generator 20.851247710175812
global_generator 12.802394330501556
local_generator 9.067587919533253
代碼可以在這里找到。
這對我來說是違反直覺的:在類實例中存儲所有狀態意味着只需要保存/恢復self
,而全局存儲所有狀態應確保零保存/恢復開銷。
有誰知道為什么類生成器和全局生成器比本地生成器慢?
當產量發生時,生成器實際上保留了實際的調用幀 。 無論您有1個還是100個局部變量,它都不會真正影響性能。
性能差異真的來自Python(這里我使用的是CPython,也就是你從http://www.python.com/下載的那個,或者你的操作系統是/usr/bin/python
,但是由於大多數相同的原因,大多數實現具有類似的性能特征)在不同類型的變量查找上表現:
在Python中實際上沒有命名局部變量; 相反,它們由一個數字引用,並由LOAD_FAST
操作碼訪問。
使用LOAD_GLOBAL
操作碼訪問全局變量。 它們總是被名稱引用,因此每次訪問都需要實際的字典查找。
實例屬性訪問是最慢的,因為self.foobar
首先需要使用LOAD_FAST
來加載對self
的引用,然后LOAD_ATTR
用於在引用的對象上查找foobar
,這是一個字典查找。 此外,如果屬性在實例本身上,這將是正常的,但如果在類上設置,則屬性查找將變慢。 您還要在實例上設置值,它會更慢,因為現在它需要在加載的實例上執行STORE_ATTR
。 更復雜的是,實例的類也需要被查詢 - 如果類恰好具有相同名稱的屬性描述符 ,那么它可以改變讀取和設置屬性的行為。
因此,最快的生成器是僅引用局部變量的生成器。 Python代碼中常見的習慣用法是將全局只讀變量的值存儲到局部變量中以加快速度。
為了演示差異,請考慮為這3個變量訪問a
, b
和self.c
生成的代碼:
a = 42
class Foo(object):
def __init__(self):
self.c = 42
def foo(self):
b = 42
yield a
yield b
yield self.c
print(list(Foo().foo())) # prints [42, 42, 42]
foo
方法的反匯編的相關部分是:
8 6 LOAD_GLOBAL 0 (a)
9 YIELD_VALUE
10 POP_TOP
9 11 LOAD_FAST 1 (b)
14 YIELD_VALUE
15 POP_TOP
10 16 LOAD_FAST 0 (self)
19 LOAD_ATTR 1 (c)
22 YIELD_VALUE
23 POP_TOP
LOAD_GLOBAL
和LOAD_ATTR
的操作數LOAD_ATTR
是對名稱a
和c
引用; 數字是桌子上的指數。 LOAD_FAST
的操作數是局部變量表的局部變量的編號 。
生成器需要保存的唯一狀態是對堆棧幀的引用,因此無論涉及多少狀態以及放置數據的位置,保存和恢復狀態都會花費完全相同的時間。
您在時間上看到的差異完全取決於Python可以訪問值的速度:本地變量訪問速度非常快,全局變量訪問需要查找全局字典中的值,因此速度較慢,並且類成員訪問需要訪問本地變量'self',然后對該值執行至少一次字典查找(同樣,對類生成器的調用必須轉換為具有單個參數的調用,該參數本身比沒有其他調用的調用慢參數)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.