multiprocessingの時間計測

Intel Core2 Duo CPUマシンでmultiprocessingモジュールを使用した場合としない場合の実行時間を比較した。Pythonは3.0を使用。2.6でもOK。

from multiprocessing import Process
import time

def fib_n_times(cnt):
    for i in range(1, cnt+1):
        print('%d => %d' % (i, fib(i)))

def fib(n): 
    if n == 0 or n == 1: 
        return n 
    else: 
        return fib(n-1) + fib(n-2)

def multi(nfib, ntimes=2):
    t = time.time()
    ps = []
    for i in range(ntimes):
        p = Process(target=fib_n_times, args=(nfib,))
        p.start()
        ps.append(p)
    for i in range(ntimes):
        ps[i].join()
    return time.time() - t

def non_multi(nfib, ntimes=2):
    t = time.time()
    for i in range(ntimes):
        fib_n_times(nfib)
    return time.time() - t
    
if __name__ == '__main__':
    nfib = 34
    t1 = non_multi(nfib)
    t2 = multi(nfib)
    print('non multiprocessing: %.3f sec' % t1)  # 27.454 sec
    print('multiprocessing:     %.3f sec' % t2)  # 14.562 sec

multiprocessingを使用しない場合は約27秒かかり、使用した場合は約14秒だった。タスクマネージャを見ていると、使用しない場合はCPUの使用率が50%〜60%くらいだったが、使用した場合はCPUの使用率がほぼ100%だった。


multiprocessingを使用しても所詮はPythonなので遅い。Cythonなどを使用してC言語の方でマルチコアに対応できる必要があると思うが、どうすれば良いのだろう?Cythonが生成したCコードをIntel C++ Compilerとかでコンパイルすればできるのかな?


使用したことながったが、これでParallel Pythonは不要になると思う。

Cython + ctypes = 最強コンボ?

Cythonのファイル.pyx内では、Pythonの関数をdefで、Cの関数をcdefで定義する。Cythonで作成したものをPythonから呼びだすには、Cythonで作成した.pydファイル(Cythonから自動生成された.cファイルをPythonのDLL化したもの)をimportして使用する。test.pydなら、import testとする。


Pythonから呼び出せる定義として、.pyxの中でPythonのdefを使用しなければならない。cdefは直接Pythonから呼びだせない。恐らくこのdefで定義された関数ではC言語化したとしてもPythonの関数呼び出しと同様になるので、実験してみた限り、Pythonから頻繁に呼び出すコードではほとんどパフォーマンスが改善されないように思える。


つまり最悪なのは、時間のかからない処理を.pyxで書いて、それを頻繁にPythonから呼びだす場合。良い使い方は、重い処理をCythonで書いて、それをPythonから少ない頻度で呼びだす場合。しかし、.pyxの部分を100% pure Cで書いた場合とのパフォーマンス差は、先日の結果から推測すると30%くらいはありそう。このオーバーヘッドはもちろん、Cythonが汎用的であることに対する代償である。


しかし、Cythonの本来の使い方は、ちょっとした拡張を簡単に書いたり、Cで書くと面倒な処理をPythonに似た文法で書けるというところにメリットがあると思う。パフォーマンスが最重要な場合はやはりCythonを使用すべきではないと思う。結局、パフォーマンスを上げたい場合はやはり100% pure Cで書かないといけない(もちろんC++でもOK)。しかし、C言語Pythonとのつなぎの部分の記述が面倒くさいという問題が残る。


そこで以下のように考えた。簡単なテストではうまくいった(但しWindows限定でテストした)。

  • 100% pure Cで書いて通常のやり方でDLL化する
  • .pyxの中でPythonとデータをやり取りする関数を定義する
  • その関数の中でctypesを利用しCのDLLで定義された関数を呼び出す
  • .pydを呼び出すPythonのコードを書く


つまりは、PythonとCのDLLのつなぎの部分にCythonを利用するというやり方。通常はSWIGを使用するところの代わりにCython + ctypesのコンボを使うことで簡易化したということ。もしくは、Cで実装するのが面倒な部分全てを.pyxで書いて、残りをctypesによるCのDLLを使用するというように切り分けても良いかもしれない。


この方法の最大の問題は、.pyxとctypesによる関数の変数のやりとりが面倒な場合は、その部分のコードが複雑になってしまうということである。例えば、CではPythonの辞書を使用できないので、.pyxの中でCの関数が理解できる引数に変換して渡してあげないといけない。Cythonのみを使う場合は、CythonがCで定義されたPythonの辞書に変換してくれるので、そのまま使用できる。


Cythonのみを使用する時に私が問題点だと思うのは以下の3つ。

  1. 100% pure Cとの速度差
  2. generatorやネスト関数など全てのPythonの機能を使用できない(バージョン0.9.8.1.1現在)
  3. 回避不能なバグや速度などの問題が残ったままCythonのプロジェクトが終了してしまって移行したくなっても、大量のコードを書いていたら簡単にCコード移行できない


但し2番目は改善作業中ということでバージョンが上がって対応してくれれば問題でなくなる。1番目の方も気にならないくらいの速度差まで最適化されるようになってくれればOK。開発効率を考えなければ結局、最強なのは、CythonやSWIGに頼らずCで自力で拡張モジュールを作成することだと思う。


あと気になるのは、Cのコードを64ビット化やマルチコア化するする場合にCythonで問題にならないのかということ。要はどこまでCythonのプロジェクトが育つかということかもしれない。