読者です 読者をやめる 読者になる 読者になる

Kesin's diary

プログラミングの記事がメインです

マルチコアで形態素解析を行う2(multiprocessing編)

前回の続きで今回はPython形態素解析をマルチコアで行うプログラムを実際に書いてみます。

私自身multiprocessingモジュールはまだ全然理解できていませんが、並列に実行するプログラムを書くには

  • Processクラス
  • Poolクラス

を使う必要があるようです。おそらくProcessが元でPoolクラスはそれを使いやすくしたものだと思います。ここではPoolクラスを使います。

Poolクラスは指定した数だけプロセスを立ち上げ、実行させたい関数と引数を放り込むことで裏で自動的にプロセスごとに振り分けてくれる仕組みのようです。リファレンスによると方法はいくつかありますが、ここではmap()を使います。map()は1つの関数に対してリストの引数を1つずつ与えながら並列に実行させます。

今回のサンプルではファイルパスが格納されているリストを渡して、並列に実行する関数の中でファイルの中身の文書を形態素解析で区切り、単語が登場した回数を数えます。前回にも説明しましたが、異なる文書ファイル間の形態素解析の結果には何の関係もないので並列に実行しても何も問題はありません。


*Poolクラスにはローカル関数を実行させることができないという制限があるようです。
d:id:halhorn:20100904
さらに私はファイルオブジェクトを引数に渡せないというのも経験しました。複雑なことをやりたいならProcessクラスを使って自分で書くしかないみたいですね。


今回作ったサンプルコードを載せます。使用したPCはMacBook Core 2 Duo(2コア)です。文書は宮沢賢治の「銀河鉄道の夜」をUTF-8に変換したものを使用して、これを100回ループで形態素解析させました。
mecab_multi.py

#coding: utf-8
from multiprocessing import Pool
import MeCab, os
from time import time

def count_word(file_name):
    dict={}
    list=[]
    file = open(file_name)
    string = file.read()
    t = MeCab.Tagger()
    node = t.parseToNode(string)
    while node:
        if node.surface in dict:
            dict[node.surface] += 1
        else:
            dict[node.surface] = 1
        node = node.next
    print "pid: %d, file: %s" % (os.getpid(), file_name)
    #登場回数が多い単語順にソート
    for word, count in sorted(dict.items(), key=lambda x:x[1], reverse=True):
        list.append(str(count)+"\t"+word)
    return list
    
if __name__ == '__main__':
    FILE = "gingatetsudono_yoru.txt"
    
    #1コアによる処理
    t = time()
    count_list=[]
    for i in xrange(0, 100):
        count_list.append(count_word(FILE))
    single_time = time()-t
    print "single: %f sec" % (single_time)
    
    #複数コアによる処理
    t = time()
    #Pool(None)は自動的にcpu_count()の値が入る
    p = Pool()
    count_list=[]
    file_list = []
    for i in xrange(0, 100):
        file_list.append(FILE)
    count_list = p.map(count_word, file_list)
    multi_time = time()-t
    print "multi: %f sec" % (multi_time)
    
    print "single: %f sec" % (single_time)

実行結果

pid: 8096, file: gingatetsudono_yoru.txt
pid: 8096, file: gingatetsudono_yoru.txt
・・・中略・・・
pid: 8096, file: gingatetsudono_yoru.txt
pid: 8096, file: gingatetsudono_yoru.txt
single: 24.089692 sec
pid: 8101, file: gingatetsudono_yoru.txt
pid: 8102, file: gingatetsudono_yoru.txt
・・・中略・・・
pid: 8101, file: gingatetsudono_yoru.txt
pid: 8102, file: gingatetsudono_yoru.txt
pid: 8101, file: gingatetsudono_yoru.txt
pid: 8101, file: gingatetsudono_yoru.txt
multi: 16.679319 sec
single: 24.089692 sec

並列に実行させるとちゃんと高速化されてますね!そして普通に実行させたときはプロセスIDが毎回同じなのに対して、並列に実行させると二つのプロセスIDが確認できます。
残念なのは私のPCが2コアしかないのでプロセスの数を変えて時間を測定することができないことです・・・。今ならクアッドコアも珍しくなくて論理的には8コアとかのCPUも普通なので試してみたいなぁ