メニーコア時代を見据えて、Pythonで並列プログラミングできるか、少し確かめたくなりました。
いくつか試行錯誤したのですが、結果から言うと、Parallel Pythonというのを使えば割とらくに並列プログラミングできることもあるかもしれない、という感じでした。
お題として使ったプログラムは、指定した拡張子を持つファイルから、指定した文字列を探すというものです。
まず、並列化していないもの。
import sys
import os
import os.path
import time
if __name__ == '__main__':
if len(sys.argv) == 1:
print "hoge pat exts..."
sys.exit(0)
pat = sys.argv[1]
exts = sys.argv[2:]
t0 = time.time()
for root, dirs, files in os.walk('.'):
for filename in files:
for ext in exts:
if filename.endswith(ext):
filepath = os.path.join(root, filename)
f = file(filepath, "r")
for linenumber, line in enumerate(f):
if line.find(pat) >= 0:
print "%s(%d): %s" % (filepath, (linenumber + 1), line.rstrip())
f.close()
t1 = time.time()
print "time: %f sec" % (t1 - t0)
つぎに、これをParallel Pythonを使って並列化(マルチプロセス化)したもの。
import sys
import os
import os.path
import time
import pp
def findpat(pat, filepaths):
for filepath in filepaths:
f = file(filepath, "r")
for linenumber, line in enumerate(f):
if line.find(pat) >= 0:
print "%s(%d): %s" % (filepath, (linenumber + 1), line.rstrip())
f.close()
# tuple of all parallel python servers to connect with
ppservers = ()
#ppservers = ("10.0.0.1",)
if __name__ == '__main__':
if len(sys.argv) == 1:
print "hoge ncpus pat exts..."
sys.exit(0)
ncpus = int(sys.argv[1])
pat = sys.argv[2]
exts = sys.argv[3:]
t0 = time.time()
job_server = pp.Server(ncpus, ppservers=ppservers)
#print "Starting pp with", job_server.get_ncpus(), "workers"
filepaths = []
jobs = []
for root, dirs, files in os.walk('.'):
for filename in files:
for ext in exts:
if filename.endswith(ext):
filepath = os.path.join(root, filename)
filepaths.append(filepath)
if len(filepaths) > 50:
jobs.append(job_server.submit(findpat, (pat, filepaths), (), ()))
filepaths = []
if len(filepaths) > 0:
jobs.append(job_server.submit(findpat, (pat, filepaths), (), ()))
filepaths = []
for job in jobs:
job()
t1 = time.time()
print "time: %f sec" % (t1 - t0)
この2つを、手元にある2コアのCPUのPCで、OpenOfficeのソースコードから「long long」という文字列を探すのに使ってみました。何回か繰り返し実行して、平均を取ってみます。
並列化していない上のやつは、18秒くらい。並列化した下のやつは、10秒くらい。ということで、確かにパフォーマンス稼げてます!
ただし、やっぱり純粋に関数型プログラミングにはなりきれてません。
具体的に言うと、最初、ファイルごとにジョブで処理するようなルーチンを書いてみたのですが、全く速くなかったので、ファイルが50個見つかるまで待って、それらに対する処理を1つのジョブにするように書き換えました。
最初のコードで遅くなった原因は多分、ジョブを作るオーバーヘッドが大きいことでしょう。さっさと泥臭いコードに書き換えてしまうところが、私が富豪プログラマになりきれないところですね・・・
意外だったのは、上の例は単純な文字列検索なので、CPUよりファイルI/Oやメモリがボトルネックになっているのではないか?という予想があっさり覆されたこと。
まあC++やJavaで実験すれば結果は違うかもしれませんが・・・そこはそれ、富豪プログラミングということで・・・
ちなみに、Parallel Pythonのサイトはこちらです。
http://www.parallelpython.com/