パンダの休日

雑多な書き残し

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
こんにちは。
Pythonでプログラミング始めてから感じたことなんですが、最近のCPUは複数コアが当たり前になっていて重いプログラムを書いて実行してもCPU使用率100%にできなくて面白くないんですよね。
うちだとCore i7が入っているので見かけ上8CPUのうち1つだけがフルに使われて数字では13%にしかなりません。
効率が悪いように感じるのはもちろんですが、これではプログラミングの醍醐味である征服感や支配感を13%しか享受できません。
若かりし頃、果敢にもCプログラミングに挑戦した(そして挫折した)あの頃は1CPU1コアしかなかったので重いプログラムを書けば100%でCPUが動き他の動作など一切受け付けないような状態が作れて非常に興奮したものです。
あれから幾年、再びプログラミングを始めて、技術の進化がこんなところからもロマンを奪ったのかと愕然としました。
で、、、
とりあえず「CPUを100%で回したい!」の欲望を叶えつつ、多少は役に立つ並列処理の簡単な手順を調べたのでメモ書きしておきます。

なんせCPU100%で使えればそれでいいのでなるべく簡単な方法を使います。
環境はPython3.2以降です。2系でも大差なかったはずです。
Pythonにはmultiprocessingという並列処理のための標準ライブラリがあるので素直にこれを使います。
multiprocessingの中にも色々機能はありますが、必要最低限のものだけをimportして使います。

まずPoolを使う方法。
これは関数に数値をずらしながら渡して結果をリストで欲しい場合などにPool.mapとともに使うと良いでしょう。

from multiprocessing import Pool

def egg(ppp):
#子プロセスが行う処理
for i in range(100000000):
pass
return (ppp**2)

if __name__ == "__main__": 
#__main__であることが必須なのでここは端折ってはいけない
p = Pool(4) #この数字がプロセスの数となる
sq_list = p.map(egg, range(20))
print(sq_list)

0から19の数の2乗をリストにしたものが表示されます。
map(func,iterable)はリストやタプルなどのiterableから1つずつ要素をfuncに渡して返ってくる戻り値でリストを成します。
途中の1億回のpassは全く不必要ですが負荷をかけるためだけに実行します。(これぞロマン)
これは余談ですがrange(1億)はPython 2.xではイテレーターではなくリストになるので2系では使わない方が良いです。
Pool(n)のnが生成する子プロセスの数です。
個々に書いた数と親プロセスの数だけPythonが実行されます。(タスクマネージャーのプロセスなどで確認できる)
積み上がっている20の仕事を上司の命令で部下4人が順に片付けるイメージです。
Core i7は見かけ上8CPUなので4プロセスで50%の使用率となります。(部下8人中4人しか仕事をしない)
Python使いの感覚としてはリスト内包表記を勝手に分散してやってくれるという感じです。
これが最小限のコードでCPU使用率100%を達成できる構成かと思いますが、もう少し実用性を上げるためにProcessというクラスも使ってみます。
次は「サイコロを3つ振った時の目の和がいくつか」、を1億回シミュレートしてみます。
早い話が桃鉄の特急カードへの期待度です。

from random import randint
from multiprocessing import Process,Queue

def tokkyu(q):
deme_list = [0]*16
for i in range(20000000):
deme_list[randint(1,6)+randint(1,6)+randint(1,6)-3] += 1
q.put(deme_list)

if __name__ == "__main__":
q = Queue()
process_list = [Process(target=tokkyu, args= (q,)) for i in range(5)]
#argsにはタプルを渡す必要があるので(q,)としている
for i in process_list:
i.start()
for i in process_list:
i.join()
sq_list = [q.get() for i in range(len(process_list))]
#get()を呼び出す回数 = プロセスの数
kekka_list = [sum([i[j] for i in sq_list]) for j in range(16)]
#それぞれのプロセスの結果を集計
for i in kekka_list:
print(i)

簡単ですが本格的な並列処理です。
Process(target=func,args=tuple)でプロセスを用意し、順に.start()で実行させます。
.join()は.join()された全てのプロセスが終了するまで先の処理に進まないようにします。
「ゴール手前でビリの奴待って全員でお手手つないでゴール」です。
今回は直後でQueueをプロセスの数だけ拾っているのでjoinがなくても先には進みませんが、サブプロセスに関わらない式はメインプロセス上でいつも通り次々に処理されるので大抵の場合はjoinしておいた方が良いようです。
Queueはプロセスを越えて扱える入れ物です。
.put()で入れて.get()で出します。
Queue.get()は入ったのが早かった順に呼び出すので結果のリストはバラバラの順番になります。
さっきは20だったのになぜ5かと言えば面倒だからです。
Processの個数が子プロセスの個数となるので20などと指定するとCPU数を越えてプロセスが生成されることになります。
20でも動きはしますが速くはなりませんし、これが100や1000になるとCPU使用率100%は当然ながらメモリが足りなくなります。
これを100などにしようとするならばforの中でProcessを8個ずつなどで作って処理して結果をリストに追加していくような感じになるかと思います。
今回はグラフ化したかったのでコマンドラインで"protest.py > tokkyu.csv"と書けばCSVが作れるような出力にしてあります。
そのグラフがこちら。
tokkyu.png


以上のような感じでコードの規模で言えば非常に簡単に並列処理が使えます。
一度作ればコピペで使えるので部分的な計算を分散させるも良し、何百万回もシミュレーションを行うも良し、無駄にCPUを回してPCを暖房に使うも良し、Pythonの使い方の幅が広がります。
もちろんmultiprocessingには他にも色々機能があるのでより厳密な使い方もできますが、素人的にはこんなもんでCPUを無駄に振り回せば十分楽しめるでしょう。
少なくとも僕としては満足したのでこれ以上は調べませんし凝りません。
似たような境遇のライトユーザーの役に立てれば幸いです。
スポンサーサイト

shader

Author:shader
多趣味飽き性に振り回されて色々やってます。

直接連絡したい方は御気軽にメールしてください。 3日以内には返信させて頂きます。

名前:
メール:
件名:
本文:

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。