2009年1月21日水曜日

Python: threadingでマルチコアCPUを有効に使う

このブログ記事をはてなブックマークに追加

動画変換などのCPU時間のかかる処理を複数コアで並列処理すれば効率的だという話。例えばIntelのCore 2 Duoの場合、常に2プロセス走っていればシングルの半分の時間で済む。というわけでPythonでやってみた。

Pythonではthreading.ThreadとBoundedSemaphoreを使って制御するのが楽そうだ。プロセスの実行はsubprocess.Popenを使う。

ここでは、複数のAVI動画ファイルをmencoderを使ってMPEGファイルに変換してみる。まず、Threadクラスを継承したMyThreadクラスを作る。また、BoundedSemaphoreで同時に実行するプロセス数を指定する。とりあえずコア数と同じ2とした。そして動画ファイル分だけMyThreadのインスタンスを作り実行する。これだけ。ちなみにセマフォではwith文を使っているがPythonのバージョンが2.5未満であればaquire/releaseに置き換えること。

実際にCore 2 Duoを積んだPCで6つの動画ファイルを変換したところ、1プロセスでは118秒かかったが2プロセス同時では60秒で済んだ。最近ではマルチコア、メニーコア全盛なのでこういった工夫は必要不可欠になってきている。

本エントリの最後にソースコードを示す。ファイル名をmy_thread.pyとすると、

my_thread.py *.avi

のように実行する。

#!/usr/bin/env python from __future__ import with_statement import sys, os, glob, subprocess, threading, time pool_sema = threading.BoundedSemaphore(2) class MyThread(threading.Thread): def __init__(self, filename): self.filename = filename threading.Thread.__init__(self) def run(self): with pool_sema: self.log = subprocess.Popen("mencoder -ovc xvid -xvidencopts bitrate=272 %s -o %s.mpg" % (self.filename, os.path.splitext(self.filename)[0]), shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.read() def main(args): if len(args) < 2: print >>sys.stderr, "Usage: %s movie-files..." % os.path.basename(args[0]) sys.exit(1) start_time = time.time() filenames = [] for arg in args[1:]: filenames.extend(glob.glob(arg)) for f in filenames: MyThread(f).start() while threading.activeCount() > 1: time.sleep(0.5) print "Time: %fs" % (time.time() - start_time) if __name__ == "__main__": main(sys.argv)

2 コメント:

匿名 さんのコメント...

pythonはGILを採用しているため、単にマルチスレッドを使っただけではマルチコアの恩恵にあやかることはできません。

上記プログラムが結果的に早くなっているのは、マルチスレッドが効いたのではなく Popen で子プロセスを起動しているせいです。

nox さんのコメント...

コメントありがとうございます。

おっしゃる通り、Pythonのマルチスレッドはそのままでマルチコアを使った並列処理にはなりません。なので、マルチコアを利用するのにPopenによる子プロセスをマルチスレッドで制御するのが楽かと考えました。

並列処理に関してあまり詳しくないので、もっと良い方法などがあるのでしたら教えてもらえると助かります。