Linux 環境でマルチコア CPU を利用した python プログラムを書く
マルチコア CPU を利用した python プログラム、と言っても、Linux 環境ではプロセス単位で CPU が割り当てられるので、つまりはマルチプロセスを意識したプログラミングになります。java でマルチコア CPU を利用しようとするとマルチスレッドで実現することになりますが、python の場合はわかりやすく別プロセスで実現されているため、ps や top コマンドで見えて面白いです。
python ではマルチプロセス用に multiprocessing モジュールが用意されていますが、今回はひとまず古典的な fork() 関数を使い、実際に複数のプロセスがマルチコア CPU によって処理されている様子を見てみたいと思います。
どちらかというと新人さん向けのプロセスのお話になってしまいましたが、季節柄ということで。
fork() によるマルチプロセスプログラミング
ソースコードはこんな感じ。複数の別プロセスを生成し、それぞれに一定時間ループ処理をさせるものです。
forkProcess.py
#!/usr/local/bin/python # -*- coding: utf-8 -*- import os import sys import random import time def hardWork(): # 処理時間を乱数で決める # random.random() は frok() 時に乱数生成器ごとコピーされ、 # 別プロセスでも同じ乱数が返されるため、かわりに # random.SystemRandom() を使う interval = random.SystemRandom().randint(5, 30) print 'Child works for %f sec' % interval end = time.time() + interval # 時間内はひたすらループする i = 0 while time.time() < end: i = i + 1 if 10000 < i: i = 0 return interval if __name__ == '__main__': # 子プロセスを 4 つ生成する for i in range(4): pid = os.fork() # fork() の返り値は生成した子プロセスのプロセス ID # 親プロセス(pid: 0)でなければ、子プロセス用の処理を行う if pid == 0: hardWork() sys.exit() # 子プロセスの実行が終わるまで待機する os.wait() print 'Parent end.'
複数プロセスで処理を行う
プログラム内部で別のプロセス(子プロセス)を生成するには、fork() 関数を使います。これは C 言語にも同じ動作をする関数があるほど伝統的な関数です。fork() を呼び出すと、それ以降のソースコードは、fork() を実行したこのプロセス自身(親プロセス)と、生成した子プロセスの 2 つに同時に実行されていくことになります。fork() 呼び出し時点での変数値、処理行を記憶するスタックトレースなどがまるごとコピーされ、新しく生成された子プロセスに引き渡されます。
シングルコア CPU のマシンでは、複数プロセスが実行されている場合、ひとつの CPU がすべてのプロセスを順番に(一定時間、一定処理量ごとに)処理していきます。その切り替えが人間の感覚ではとらえにくいほど細やかなため知覚しにくいですが、突き詰めれば同時実行されているわけではありません。なので、あるプロセスが重い処理を行っていると、CPU の処理能力はそのプロセスに占有され、OS を含めた他のプロセスも引きずられて重くなります。
マルチコア CPU のマシンでは、プロセス毎に CPU のコアが割り当てられるため、本当の意味での同時実行が実現されます。複数のプロセスに割り当てられた CPU が異なる場合、各 CPU は他の CPU に割り当てられたプロセスについては処理しなくていいため、並列で捌いていくことができます。
Linux ではプロセスが確保したメモリやスレッドの関係で、特定の CPU に高負荷なプロセスが集中するなどしない限りは、割り当てられた CPU が変更されることはありません。
搭載されている CPU の数を調べる
Linux ではマシンの CPU 情報は /proc/cpuinfo の内容から調べることができます。processor の欄には CPU に割り当てられた番号(0 ~)が記載されていて、これを数えることでコア数を確認できます。
$ cat /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 23 model name : Intel(R) Core(TM)2 Quad CPU Q9650 @ 3.00GHz stepping : 10 microcode : 0xa0b cpu MHz : 2000.000 cache size : 6144 KB physical id : 0 siblings : 4 core id : 0 cpu cores : 4 apicid : 0 initial apicid : 0 fpu : yes fpu_exception : yes cpuid level : 13 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx lm constant_tsc arch_perfmon pebs bts rep_good nopl aperfmperf pni dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm dtherm tpr_shadow vnmi flexpriority bogomips : 5998.87 clflush size : 64 cache_alignment : 64 address sizes : 36 bits physical, 48 bits virtual power management: processor : 1 vendor_id : GenuineIntel cpu family : 6 model : 23 model name : Intel(R) Core(TM)2 Quad CPU Q9650 @ 3.00GHz ...(以下略)...ここで確認できるコアの数は論理的なもので、実際に CPU に搭載されている物理コアの数とは異なる場合があります。近年の CPU の多くにはハイパースレッディングと呼ばれる機能があり、これは、1 つのコアを論理的に 2 つのコアにみせかけて利用できる技術です。ハイパースレッディングが有効で物理コアが 2 つの場合、/proc/cpuinfo からは 4 つの論理コアが確認できます。
マルチプロセスの動作を確認する
上記サンプルプログラムは、親プロセスから 4 つの子プロセスを生成するようになっています。これを 2 コアのマシンで実行してみます。$ python forkProcess.py Child works for 29.000000 sec Child works for 27.000000 sec Child works for 9.000000 sec Child works for 21.000000 sec Parent end.親プロセスは os.wait() 関数により、すべての子プロセスが終了するのを待ちます。fork() で生成された 4 つの子プロセスはそれぞれランダムな時間を処理し続け、その途中、最大 5 つのプロセスが並列で処理されていることになります。
forkProcess.py を実行後数秒の段階で、ps コマンドで実行されている python プロセスを調べてみました。
$ ps -C python -o pid,ppid,psr,%cpu,stat,start,time,args PID PPID PSR %CPU STAT STARTED TIME COMMAND 4425 2456 0 1.5 S+ 17:00:50 00:00:00 python forkProcess.py 4426 4425 1 42.3 R+ 17:00:50 00:00:02 python forkProcess.py 4427 4425 1 42.3 R+ 17:00:50 00:00:02 python forkProcess.py 4428 4425 0 44.3 R+ 17:00:50 00:00:02 python forkProcess.py 4429 4425 1 42.6 R+ 17:00:50 00:00:02 python forkProcess.pyPID がプロセス毎にユニークなプロセス ID で、PPID が親プロセスのプロセス ID となります。4426 ~ 4429 の 4 つは親のプロセス ID が 4425 となっていて、4425 のプロセスから fork() した子プロセスであることがわかります。COMMAND の欄には実行コマンドが表示されます。fork() を使うと、この実行結果の通り、親プロセスのコマンドがそのまま子プロセスに引き継がれています。
通常の ps コマンドでは表示されませんが、-o オプションに psr を指定すると、プロセスの情報として割り当てられた CPU の番号を表示することができます。全部で 5 つのプロセスが、0 番、1 番の別々の CPU に割り当てられ、実行されていることがわかります。
Linux, python コメント (0) 2013/04/16 19:42:27