原文網址: 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 中被修正。
現今的電腦運算最引人關注的問題之一就是如何省電。可攜式裝置對電力的使用更是錙銖必較。 當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 中被修正。
沒有留言:
張貼留言