まずは、昔のフィボナッチ数列の計算するコードを持ち出します。
import time
def fibonacci(n):
value = 0
f1 = 1
f2 = -1
for i in range(n+1):
value = f1 + f2
f2 = f1
f1 = value
return value
if __name__ == "__main__":
for x in range(3):
fibonacci(3)
time.sleep(0)
start = time.time()
for x in range(4000, 5000):
fibonacci(x)
end = time.time()
print end - start
これを実行すると、僕のMacBook Airだと今日は大体1.11秒です。Java(Jython?)などのちゃんとしたスレッドを持ってる人たちは、ループの中をスレッドにするともっと高速になるんじゃないか?って考えます。なので、スレッドにしちゃいましょう。import time
import threading
def fibonacci(n):
value = 0
f1 = 1
f2 = -1
for i in range(n+1):
value = f1 + f2
f2 = f1
f1 = value
return value
if __name__ == "__main__":
for x in range(3):
fibonacci(3)
time.sleep(0)
thread_list = []
start = time.time()
for x in range(4000, 5000):
thrd = threading.Thread(target=fibonacci, args=(x,))
thread_list.append(thrd)
thrd.start()
for thrd in thread_list:
thrd.join()
end = time.time()
print end - start
さて、これを実行すると1.7秒ぐらいです。参考までにCPythonで実行すると2.01秒でした。さらに参考までにPython3で実行すると2.37秒でした。Python3ダメダメじゃん。
(;゜〇゜)はっ!これはPython3のエントリじゃないからこれ以上Python3のことを書いたら団長に怒られる!
さて、スレッドにした方が遅いですね。まあ、何も不思議じゃなくって、GILとかあるので、想定の範囲内です。ちなみに、1000個スレッドがあると、LinuxやMacはメモリは見かけ上8Gぐらいなります。まあ、単純な計算だけならスレッドにするより何も考えずにシーケンシャルに行った方が良いと言うことですね。もしくはプロセスのフォークで・・・。でも、途中でネットワークとかブロックするような処理があると、シーケンシャルにやると遅くなっちゃいます。
ここまでが前置きです。結論はないです。Application-level Stackless featuresにcontinuletとgreenletについて書かれています。どっちもマイクロスレッドですね。continuletのレイヤの上にgreenletと同じAPIのモジュールを実装しているようです。32bit環境ではスレッドあたり4KBのメモリ使用量だって書いています。本物のスレッドに比べると遙かに少ないです。
と言うことで、こんなコードを書いてみました。
import time
from greenlet import greenlet
def fibonacci(n):
value = 0
f1 = 1
f2 = -1
for i in range(n+1):
value = f1 + f2
f2 = f1
f1 = value
return value
if __name__ == "__main__":
parent = greenlet()
start = time.time()
grs = [greenlet(fibonacci, parent) for _ in range(1000)]
i = 4000
for gr in grs:
gr.switch(i)
i += 1
end = time.time()
print end - start
実行すると1.2秒ぐらいです。最初のコードが1.1秒ぐらいなので0.1秒ぐらいのオーバーヘッドですね。まあ、待ちがないマイクロスレッドでスレッドの途中でコンテキストの切り替えがないので、つまんないですね。ちょっとこれはいろいろやり過ぎているので、もっと単純なコードでやってみましょう。
import threading
import time
def test2():
pass
start = time.time()
thrds = [threading.Thread(target=test2) for i in range(1000)]
for thrd in thrds:
thrd.start()
for thrd in thrds:
thrd.join()
end = time.time()
print end - start
単にスレッドを作って空の関数を実行しているだけです。これだけのコードで0.29秒です。感想としては、遅い・・・。CPython2.7だと0.14秒です。うっ、pypy負けてる。と言うかJITがうまく動かなさそうなコードなのでいいです。ちなみにPython3でも0.14秒ぐらいでした。
それではgreenletで。
from greenlet import greenlet
import time
def test1(x, y):
for gr in grs:
z = gr.switch(2)
def test2(u):
#print id(greenlet.getcurrent())
gr1.switch(42)
start = time.time()
gr1 = greenlet(test1)
grs = [greenlet(test2) for x in range(1000)]
gr1.switch(1, 2)
end = time.time()
print end - start
このヒトを実行すると0.07秒です。スレッド重いですね。
