『Effective Python』の続き。不遇のWindows。 www.effectivepython.com
人によってはGlobal Interpreter Lock(GIL) と聞くと嫌な思い出が蘇ることがあるのだろうか。 私は恥ずかしながら並列・並行プログラミング未経験というアレな状況なのでGILには特に嫌な思い出はない。
素因数分解を例にマルチスレッドで高速化したいと夢見て、
from time import time from threading import Thread def factorize(number): for i in range(1, number + 1): if number % i == 0: yield i numbers = [2139079, 1214759, 1516637, 1852285] start = time() for number in numbers: list(factorize(number)) end = time() print('Took %.3f seconds' % (end - start))
という直列バージョンと、
class FactorizeThread(Thread): def __init__(self, number): super().__init__() self.number = number def run(self): self.factors = list(factorize(self.number)) start = time() threads = [] for number in numbers: thread = FactorizeThread(number) thread.start() threads.append(thread) for thread in threads: thread.join() end = time() print('Took %.3f seconds' % (end - start))
というマルチスレッドによる並列バージョンを実行して どちらも大差ない というつらい現実が紹介されている。
そんな悪名高い?GILにも利点があり、その中でもブロッキングI/O(同期I/O)に使うとよい、と述べている。
問題は、その例が Windowsでは動作しない のである。
import select def slow_systemcall(): select.select([], [], [], 0.1)
18.3. select — I/O 処理の完了を待機する — Python 3.4.3 ドキュメントには次のように書いてある。
引数に空のシーケンスを指定してもかまいませんが、3 つの引数全てを空のシーケンスにしてもよいかどうかはプラットフォームに依存します (Unix では動作し、Windows では動作しないことが知られています)。
並行・並列処理が何故難しいのか、それはシステム寄りの部分の理解がより一層求められるから、ではと仮説を立てている。 今まではPython側がプラットフォーム側の差異を吸収してくれていたがこの辺りになると吸収しきれずUnix系とWindowsで動きが異なってくる。 もっとも、Unix系で実装する分にはそこまで辛い思いをする必要はなさそうである。 問題はWindowsであり、中々大変な道のりが待っていそうである。