RSS2.0

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.py
PID がプロセス毎にユニークなプロセス ID で、PPID が親プロセスのプロセス ID となります。4426 ~ 4429 の 4 つは親のプロセス ID が 4425 となっていて、4425 のプロセスから fork() した子プロセスであることがわかります。COMMAND の欄には実行コマンドが表示されます。fork() を使うと、この実行結果の通り、親プロセスのコマンドがそのまま子プロセスに引き継がれています。

通常の ps コマンドでは表示されませんが、-o オプションに psr を指定すると、プロセスの情報として割り当てられた CPU の番号を表示することができます。全部で 5 つのプロセスが、0 番、1 番の別々の CPU に割り当てられ、実行されていることがわかります。
  Linuxpython  コメント (0)  2013/04/16 19:42:27


公開範囲:
プロフィール HN: ももかん
ゲーム作ったり雑談書いたり・・・していた時期が私にもありました。
カレンダー
<<2019, 5>>
2829301234
567891011
12131415161718
19202122232425
2627282930311