2011年7月24日 星期日

輪詢對 future 模組及平行處理的影響

原文網址: Of polling, futures and parallel execution

現今的電腦運算最引人關注的問題之一就是如何省電。可攜式裝置對電力的使用更是錙銖必較。 當CPU閒置時,CPU可以進入各種低耗能的狀態。閒置的狀態持續得久,就會進入更深層的低耗能狀態, 也就會更加省電。因此充一次電,你的電池就可以撐得更久。

低耗能狀態有一個致命的大敵: 輪巡( polling ),若有一個工作(task) 定時地讓CPU離開閒置狀況, 不管所做的工作有多麼的簡單(好比只是檢查記憶上的一個數值),都會讓CPU 離開低耗能狀態, 把CPU內部所有組件都喚醒,卻在工作完成很久以後才會再進入低耗能閒置狀態。 這大大地減低了電池的壽命, 而 Intel 也關注這個 問題

Python 3.2 標準程式庫加入了一個新的模組 concurrent.futures module 來平行啟動工作並且會等待工作結束。當我在看這個模組的程式碼時, 我注意到它在部份的Worker thread 及 Worker procees 之中使用了輪詢。 我說部份是因為是因為 ThreadPoolExecutor 以及 ProcessPoolExecutor. 之間的實作是不一樣的。前者每一個worker thread 都輪詢, 而後者只會在負責溝通各個 worker process 的 management thread 輪詢。

輪詢這在裡只有一個作用: 偵測該何時啟動關閉程序。 其他的工作像是把 Callable 物件放進 queue 或是把運算的結果從 queue 裡面拿出來,是使用同步的 queue。 這些 queue 物件是由 threading 或是 multiprocessing 模組所提供的, 依照不同executor 實作而會使用不同的queue。

所以,我想到了一個簡單的 解法 : 我把輪詢換成了一個 sentinel, 就是內建的 None。 當queue 收到了 None, 一個在等待的 worker, 會自然地被喚醒然後去檢查是否該被關閉。 在 ProcessPoolExecutor 的實作裡, 情況會變得比較複雜, 因為我們不只是要喚醒一個 queue management thread, 更要額外喚醒多個 worker process。

在我一開始的patch 裡面,我還是會將論詢的timeout 設定為一個非常大的區間 (10分鐘), 因此 workers 將可以在某個時間點被喚醒。 會存在這麼大的timeout, 是為了避免有 bug 的程式碼讓程式沒有辨法經由前面說到的 sentinel 來執行關閉的程序。 出於好奇心,我觀察了一下 multiprocessing 的程式碼而且發現到了另一個有趣的問題。 在windows 平台上,當 multiprocessing.Queue.get() 在設定一個非零,非無限大的timeout的時候,windows 會使用...輪詢 ( 對此,我開啟了 issue 11668 )。 這個實作方式會使用迴圈加上高頻率的輪詢, 而每一次的輪詢會增加千分之一秒的 timeout。

不用說,這種時常喚醒系統的方式讓我的 patch 在 windows 上失去了我想達到的效果。 所以,我就把那個 10 分鐘的 timeout 移除了。 因為把這個timeout 設定移除了,那不管在那一個平台, 都不會再有系統被定期喚醒的問題了。

在 Python 3.2 以前,threading 模組及使用了 worker thread 的 multiprocessing 模組中的 timeout 機制都使用了輪詢,這將會在 issue 7316 中被修正。

2011年7月17日 星期日

在 Python 2.7 and 3.x 之間的 Deprecations

原文網址: Deprecations between Python 2.7 and 3.x

最近 在 python-dev 上面的討論 提到了關於目前開發者所遇到的從 Python 2.7 到 Python 3.x 時,Python 的 deprecation 政策問題。 由於考慮到 Python 使用者通常會直接從 Python 2.7 轉移到目前的最新 3.x 版,而不會關心舊版本,開發團隊對 deprecation 方針做了修改。

背景

Python 對於向下相容性有很強烈的承諾,除非可以符合相容性方針,否則不允許做出任何改變。 基本上來說也就是原本正確的程式不能在新版本的 Python 當中出錯。然而,這並不是永遠都能做到的, 舉例來說,當有某個 API 有很嚴重的問題並且需要被其他 API 替代的時候。在這種狀況之下, Python 依照 deprecation 方針:當某些特性要正式被移除的時候,會有一年的轉換期。 在這段期間,必須要有 deprecation 警告訊息來讓開發者有時間更新他們的程式碼。 關於 Python 的 deprecation 方針的細節在 PEP 5 有詳細的說明。由於只有在新的 Python 發行版本會有改變, 而且在發行版本之間通常都間隔了 18 個月,因此一次發行的 deprecation 周期剛好符合標準。

對於這個方針的有個例外是 Python 3。從 Python 2 到 Python 3 之間的主要版號變更就是特別為了允許破壞向下相容性的變更, 讓開發者有機會可以修正在目前方針之下無法修正的問題。舉例來說,讓字串預設為 Unicode,還有回傳 iterators 而不是 lists。

平行開發

轉移到 Python 3 會花上些時間,很多人估計大概是 5 年左右,所以 Python 2 和 Python 3 會平行開發一段時間。

由於 Python 2.7 會是 Python 2 的最終發行版本,大家都同意它的維護其將會被延長一大段時間。最後, 想要移到更新版本的開發者會需要跳到 Python 3。

以下是其中一個問題...

令人驚訝的 deprecation

一個 python-dev 的討論串 當中, 有個人指出了在 C API 當中的某個特定函式 PyCObject_AsVoidPtr 被移除了而沒有出現足夠的警告訊息。 然而這跟 deprecation 方針是相違背的!發生了什麼事情?

這個變更是從舊的 API (PyCObject) 轉移到更新、更好的 API (PyCapsule) 的一部份。 問題在於 PyCObject 在 Python 2.6 當中是預設而且唯一可用的 API。它在 Python 2.7 當中已經被標為 deprecated 了。 在 Python 3.2 這個 API 並不存在並且應該使用新的 PyCapsule 。這造成了一段大約七個月的 deprecation 周期 - 從 Python 2.7 的發行 (2010 年 7 月) 到 Python 3.2 的發行 (2011 年 2 月)。這比十二個月的最短周期還短的多, 並讓開發者很難對 Python 的發行版本做支援。

對於從 3.0 升級到 3.1 接著升到 3.2 的人來說,這個 deprecation 過程沒啥問題。 Python 3.1 在 2010 年 3 月發行時有這個 deprecation,並且在 3.x 的發行系列也都有, 因此有著將近 12 個月的的 deprecation 周期。但是這並不是人們通常的作法: 他們直接從 2.7 升級到最新的 3.x 版 (在這個案例是 3.2) 而造成了這個問題。 這從來不是 python-dev 的用意,但是 PEP5 提出的時候並沒有考慮到 Python 會同時有正在積極開發的並行版本。

所以我們該怎麼辦?

儘管 PyCObject/PyCapsule API 是個明顯的問題,這並不是不可能被處理的。 但是在 python-dev 上面至少有一個人為了處理這個問題遇到了一些困難。總而言之,這一切都不應該發生。

對於``PyCObject``/PyCapsule 的特例來說,這個問題已經存在而且我們也不能做些什麼。 重新恢復 PyCObject` 並不是一個選項,這將會增加更多的不相容性。然而,儘管會很繁瑣, 一般來說是可以寫出適合當前有提供的 API 的程式碼。事實上,在 Python 3.1 中, PyCObject API 是寫成 PyCapsule 的 wrapper。有人建議說如果有任何人需要, 在 Python 3.1 中的實作可以抽出來讓第三方程式碼使用。另外,大家也同意對於這變更要寫一份「有追溯力」的 PEP, 以便敘述在這變更之後的原因,並且說明可以幫助開發者轉移的資源。

簡單來說,Python 開發團隊現在已經認知到有這個問題並且會避免它再度發生。Guido 對這個狀況 發表了評論 並且建議目前 Python 3 對於 deprecations 的使用必須要保守一點。至少 deprecated API 在被移除之前必須要存在足夠久的時間, 給予從 2.7 轉移的開發者一條路走。

間接來說,這個討論串提出了要如何在短時間內有效率的讓 Python 的變更可以跟更多的人做交流, 這也是建立這個部落格想要解決的問題。

這些代表了什麼?

首先,這代表了 Python 開發者並不一定總是對的。沒人會想要讓開發者日子過得更苦,只是沒有及時發現這個問題。

再來,解決這個問題可能會帶來更多的麻煩,所以 PyCObject API 沒有重新被啟用。 儘管重新恢復 API 可能會幫助那些被這個變更傷害到的開發者,總體來說這會讓相容性問題更加的複雜。 同時,我們必須容忍這問題並且繼續往前邁進。我們已經學到了教訓,下次不會再犯下相同的錯誤。

這件事情也表現出 Python 開發團隊希望聽取使用者的意見。相容性是相當重要的, 大家也為了可以無痛轉移到新版本盡了每一份心力。尤其是函式庫的開發者會在合理的努力下希望能夠支援多個 Python 版本。

最後,開發者並沒有拋棄 Python 2.7,儘管在 2.7 並不會有新的特性,未來也不會有 Python 2.8 ,2.7 版使用者的看法還是很有影響力的。 讓使用者可以順利轉移到 3.x 版對整個 Python 社群來說是很重要的。

2011年7月3日 星期日

2011-04-06-正規化AST的異動控管策略

原文網址: Formalizing the AST Change Control Policy

Python 在 AST 模組中公開了抽象語法樹(abstract syntax tree [1], AST)用來呈現Python原始碼編譯過的形式。 AST 模組容許使用者的程式碼在原始碼分析與編譯過的bytecode之間對AST 表達式(representation) 進行檢視與操作。

雖然Python 原始碼的語義已經在 language reference 中被定義了,AST 模組只是CPython的實作細節,在其它的Python實作版本中沒有必要去實作它。

AST模組相容性

某些 工作 中需要在AST上對CPython 中的區域程式碼優化器(peephole optimizer [2] )進行全面改寫(而不是在bytecode 這層,就這個case 而言),為此 Eugene Toder 需要對AST的結構進行修改。AST 作為一個CPython實作上的細節,沒辦法立即明瞭它是否也採用向後相容策略。 為此, Eugene向python-dev 問了這個問題 。 在修改AST時是否需要確保它的向後相容性?

一般來說社群裡一致同意 沒必要 去考量向後相容。AST 模組對外公開了一個常數 ast.__version__ , 讓使用者的程式碼得以依照AST的版本資訊對於它的程式行為自行調整。對於一個與實作版本相關的模組而言,這 樣子已經提供了足夠的相容性了。

其它 Python 的實作版本

事實上,Jython 和 IronPython 的維護者們指出他們各自的實作版本要嘛擁有一個相容的AST模組或是 傾向提供一個。即便如此,他們並不認為這代表 AST 模組應該被凍結發展並且樂見隨著 ast.__version__ 常數的更動,能在不用考慮相容性下修改AST模組。

有一個被提出的觀點指出在 test_ast.py 中的完整 test suite 可以幫助其它的實作 版本確保它們的 AST 表達式與 CPython 相容。 增加 test_ast.py 的涵蓋對於想要參與 Python 內部運作的的人們是一個好的 Project。

之後會發生什麼?

開啟這個討論串的 patch截至目前為止尚未 被整進 CPython中。 有可能,什麼事都不會發生。無論如何,如果它真的被commit了,AST 將會 以不向後相容的形式存在。 ast.__version__ 這個常數將會隨之更動來對應, 這樣子使用者們的程式碼才會知道可是程式也要隨之改動。概括來說,這將是未來AST更動 後要處理的方式。

Python 的開發者們對於這個策略被採用後,對已使用 AST的程式影響有多廣多深很有興趣。 如果有讀者們的程式會因為AST變更而受到影響,鼓勵大家去參與 在python-dev上的討論


[1]譯註: 關於 AST 可參閱 Wiki上的解釋
[2]譯註: 它的基本概念是為瞭解決statement by statement translation 可能會產生冗餘code的問題, 所以它在最佳化的時候是一次看一小段程式碼進行優化的動作,可以參閱 薛智文老師的投影片Wiki上的解釋