それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Rubyのスレッド本数制御でコマンドは早くなるのか?

スレッド化で早くなるのか試してみた

rubyのスレッド数を制御して処理するエントリを書いた⇛ruby スレッドの同時実行数を制御する - それマグで!

じゃぁ、実際に早くなるのか実験してみよう。

画像ファイル55枚で実験

画像の縮小処理とGPS情報削除をサンプルにやってみる

  1. bashで単純なコマンド起動をする
  2. コマンド起動スレッド5本まで
  3. コマンド起動スレッド10本までにする
  4. 1ファイルに1スレッド大量スレッド起動

画像ファイルはiphone5の写真2.4Mbytes が55枚。*1
実行マシンはMacbook Air Mid2011 Core i5 mem 4GB

単純なbashで起動

一番単純な *.jpg を引数にするパターン。これはbashが1ファイルずつ起動していきます。

takuya@air:~/Desktop$ time mogrify -strip -resize 800 *.jpg

real        0m32.233s
user       0m31.022s
sys	0m1.074s

この結果の秒数をベースに考えます。

rubyで5スレッドで制御

ruby で5スレッド(5個のサブプロセス起動)で処理

takuya@air:~/Desktop$ time ./test5.rb *.jpg

real	0m20.595s
user	1m4.805s
sys	    0m5.616s


期待通り早くなる

require './my_patched_thread.rb'
Thread.max_concurrent=5
ARGV.map{|f|
	Thread.new{
		`mogrify -strip -resize 800 '#{f}' `		
	}
}.each{|t| t.join }

では、10スレッドで制御

takuya@air:~/Desktop$ time ./test.rb *.jpg

real	0m19.787s
user	1m2.683s
sys	    0m5.265s
Thread.max_concurrent=10
#以下同じ

10スレッドでもそんなに早くならない。

スレッド化しない場合

takuya@air:~/Desktop$ time ./test_no_thread.rb *.jpg

real	0m35.574s
user	0m32.048s
sys	0m3.159s

ソースはこちら

ARGV.map{|f|
  `mogrify -strip -resize 800 '#{f}' `
}

これはbashとやってることが同じなのでbash起動と速度変わらない。

ファイル全部をスレッド起動

1ファイルに1スレッドを割り当てて、スレッドバンバン起動した
これは、予想以上に遅かった。

takuya@air:~/Desktop$ time ./test.rb *.jpg

real	2m28.569s
user	0m57.969s
sys	0m43.182s

55ファイルなので、55スレッドが起動して、55プロセスが同時にファイルに読み込みと書込するので、やたら遅い

ソースはこちら

ARGV.map{|f|
  Thread.start{  `mogrify -strip -resize 800 '#{f}' ` } 
}

まとめ

処理 real user sys
bashのみ 0m32.233s 0m31.022s 0m1.074s
スレッド化無し 0m35.574s 0m32.048s 0m3.159s
5スレッド 0m20.595s 1m4.805s 0m5.616s
10スレッド 0m19.787s 1m2.683s 0m5.265s
全部スレッド化 2m28.569s 0m57.969s 0m43.182s

結論

スレッド化効果ある。ruby のスレッドはディスクIO待ちで切り替わるので、そんなに早くならないかと思ったけど、それなりには早くなる。


単純にThread.new{コマンド起動}の場合は、ファイル受けて、スレッドでコマンド起動したので、ファイル数分コマンド起動するとそれだけマシンのディスクIOを奪い合って、リソース不足になって大変みたい。

でも、スレッド数制御のために面倒なコード*2が必要になるので。前エントリのやり方はそれなりに、使えるんじゃないか。

単純に、これを書くだけに収まるの、明確で嬉しい。

Thread.max_concurrent=5
ARGV.map{|f|
  Thread.new{  `mogrify -strip -resize 800 '#{f}' ` } 
}.each{|t| t.join}

スレッドは常時動いてる前提なので、こういう使い方は、「んー」って感じだけどスレッドをキューにためて、準備のできたものから順に起動するって使い方をやりたい場面は多いしスレッド作るときでも、単純に数本を並列化したいことが多いので、結構使えそうです。

*1:55枚は松井選手のニュースをたまたまニュースでやってたから。意味は無い

*2:配列を5分割したり、キューを使って5スレッド作ったり、5回Thread.new(&proc) 書いたり、処理スレッドをキューにためたて実行するスレッド実行するスレッドを別に作るとか