2011年4月26日火曜日

ポーリングについて futures と並列実行

原文はこちら: Of polling, futures and parallel execution

いまのコンピューティングの大きな関心ごとの1つは省電力です。多くの携帯デバイス (ラップトップ、タブレット、ネットブック) にとって大きな課題です。最新の CPU はアイドル状態のときに段階的に低消費電力状態に入ります。アイドル状態が長くなると、より深い低消費電力状態になりバッテリーの消費量も少なくなります。そのため、たった1回の充電でデバイスのバッテリーライフが長くなります。

低消費電力状態にはポーリングという敵がいます。あるタスクが定期的に CPU を利用するとき、潜在的変化を調べるためにメモリの位置を読み込むような些細な処理であっても、CPU は低消費電力状態から復帰してきて、その内部構造を全てを利用します。単調で定期的な実行状態はその目的の処理を完了してから随分経った後で再び低消費電力状態へ入ります。インテル社は、ポーリングという動作そのものを 電力を浪費する懸念事項と考えています

Python 3.2 は、並列タスクを起動する concurrent.futures モジュール という新たな標準ライブラリを提供します。そのライブラリは並列タスクが終わるのを待ちます。そのコードを詳しく調べてみると、ワーカースレッドやプロセスの一部にポーリングが使用されていることに気付きました。"一部" というのは、 ThreadPoolExecutorProcessPoolExecutor の実装が違っているからです。前者はそれぞれのワーカースレッドがポーリングを行います。一方、後者はワーカープロセスと通信するために使用される1つのキュー管理スレッドのみがポーリングを行います。

ここでは、ポーリングはシャットダウンの手続きが開始されるときを検出するというたった1つの処理のみに使用されていました。呼び出し可能オブジェクトをキューイングする、もしくはキューに追加されたその直前の呼び出し可能オブジェクトからの結果を取得するといったその他のタスクは、同期キューオブジェクトを使用します。これらのキューオブジェクトは、使用している Executor の実装に依存した threadingmultiprocessing モジュールのどちらかにあります。

そのため、私は シンプルな解決方法 を思い付きました。私はこのポーリングを None という組み込みのセンチネルに置き換えます。キューが None を受信すると、待ち状態のあるワーカーは自然に起動してシャットダウンすべきかどうかを確認します。ProcessPoolExecutor では、1つのキューマネジメントスレッドに加えて、N 個のワーカープロセスを起動させる必要があるといった、ちょっと厄介な問題があります。

私の最初のパッチでは、それでもポーリングのタイムアウトを持たせていました。ある時点でワーカーが起動するかなり長いタイムアウトです (10分間) 。コードがバグっているときに発生する長いタイムアウトですが、必要なときに前述したセンチネルを通してシャットダウンの通知を取得できませんでした。好奇心から、私は multiprocessing のソースコードを読み始めて、また別の興味深い見解が得られました。Windows 環境では、ゼロ以外の有限なタイムアウトをもつ multiprocessing.Queue.get() がポーリングを使用します (私は 課題 11668 を登録しました) 。それは 1 ミリ秒のタイムアウトから始まり、ループする毎に増加する高頻度のポーリングを使用します。

それでもタイムアウトを使用していると言うのは確かにそうですが、そのタイムアウトが長く、Windows 環境で私のパッチは役に立ちません。それは 1 ミリ秒毎に起動して実行するように実装されているからです。私の最新のパッチは全くタイムアウトを使用しないので、プラットフォームに関係なく定期的に起動することはありません。

歴史的に説明すると、Python 3.2 以前では、 threading モジュールの全てのタイムアウト機能や、 multiprocessing それ自身が様々なタスクのためにワーカースレッドを使用するので multiprocessing においてもそのほとんどがポーリングを使用します。これは 課題 7316 で修正されました。

0 件のコメント:

コメントを投稿