Kernelモジュールに定義されている「fork」メソッドを使うと、Rubyプログラムが自分自身の分身となるようなプロセスを生成して、別のプロセスとして処理を行わせることができます。
冒頭で「並列化による高速化を狙ってThreadクラスを使うことは難しい」と書きましたが、「fork」を行うと、OSレベルで別のプロセスが生成されます。OSは各プロセスにCPUコアを振り分けているので、「fork」によって時間のかかる処理をうまく分散化できる可能性があります。また、「fork」されたプロセスは親プロセスと別の世界で動くというメリットもあります。
ただし利点ばかりではありません。頻繁にforkすると、forkのためのオーバーヘッドが無視できなくなりますし、OSのシステムコールに依存する機能なので、forkシステムコールを持たないようなシステムでは利用することはできません。Windowsはforkシステムコールを持たないようですので、forkの恩恵にあずかることは残念ながらできません。
では、process03.rbとprocess04.rbに例を示しましょう。どちらも1億回(!)実数に「0.001」を足し込むような処理を行います。違いは、process03.rbはプロセスをフォークして実行しており、process04.rbはフォークせずに逐次実行している点です。
f = 0.0 started_time = Time.now.to_i 3.times do fork do 100000000.times { f += 0.001 } printf("%.3f\n", f) end end Process.waitall puts "it takes #{Time.now.to_i - started_time} s"
f = 0.0 started_time = Time.now.to_i 3.times do 100000000.times { f += 0.001 } printf("%.3f\n", f) f = 0 end puts "it takes #{Time.now.to_i - started_time} s"
$ ruby process03.rb 100000.000 100000.000 100000.000 it takes 12 s
$ ruby process04.rb 100000.000 100000.000 100000.000 it takes 26 s
process03.rbもprocess04.rbも大筋は同じです。まず、1行目で変数fを0.0で初期化し、2行目で現在の時刻を取得し、それを「to_i」メソッドでUNIX時刻に変換して変数started_timeに格納します。
続く4行目からの「3.times」とそのブロックで、process03.rbではプロセスを3回フォークしています。「fork」メソッドのブロック引数として、fに1億回「0.001」を足し込むような処理を与えています。process04.rbは、単に逐次的に1億回「0.001」を足し込むような処理を行っています。
最後の行で、どちらのスクリプトも、現在のUNIX時刻と開始時のUNIX時刻の差を取り、処理時間を算出しています。
「htop」というツールを使って、process03.rbとprocess04.rbの実行の様子を比べてみましょう。
ご覧の通り、フォークした場合は「ruby process03.rb」というプロセスが3個立ち上がっていることが分かります。たいていの現代的なOSでは、CPUに負荷を掛けるようなプロセスはなるべく別々のCPUコアに割り当てるようにするので、process03.rbの方が処理時間も短く済んでいます。
また、子プロセスはフォークされた時点で、それぞれメモリ空間などが異なる世界で動くことになります。ですので、process03.rbの6行目で変数fを書き換えても、他の子プロセスの変数fに影響することはないので、きちんといずれの子プロセスでも「100000.000」という計算結果が得られています。
今回は、Rubyのスレッド、ファイバー、プロセスといった仕組みを学びました。特に、RubyのIOクラスやforkメソッドといった仕組みは、OSそのものと深くつながっています。
例えばforkメソッドですが、UNIX系OSの標準的な「システムコール」の中に、ズバリ「フォーク」というものがあります。今回はあまり詳細に立ち入らずに説明したつもりですが、少し難しかったかもしれません。この辺りの話を深く理解するためにはOSの仕組みやシステムコールを学ぶ必要があるので、スキルアップのためにも、ぜひ勉強することをお勧めします。
次回はRubyの黒魔術、メタプログラミングについて解説したいと思います。メタプログラミングについて理解することは、初心者から一歩踏み出して、エキスパートになるたには必須です。お楽しみに!
麻田 優真(Rails技術者認定シルバー試験問題作成者)
イタリア、ローマ生まれ。中学生のころHSPに初めて触れ、プログラミングの楽しさを知る。オープンソースやハッカーカルチャーを好む。C#からRubyに転向したときに、動的型付け言語の柔軟性やメタプログラミングの魅力に感動し、Rubyとともにプログラマーとしての人生を歩む決意を固める。
現在は奈良先端科学技術大学院大学で学生として所属するかたわら、株式会社アジャイルウェアでプログラマーとして従事。Ruby on Railsによるコンシューマー向けのWebサービスの開発などに尽力している。
好きなメソッドは、define_method。
Twitter:@Mozamimy、ブログ:http://blog.quellencode.org/
山根 剛司(Ruby業務開発歴7年)
兵庫県生まれ。1997年からベンチャー系のパッケージベンダーで10年間勤務。当時、使用していた言語はJavaとサーバーサイドJavaScript。
2007年よりITコンサル会社に転職し、Rubyと出会って衝撃を受ける。基幹システムをRuby on Railsで置き換えるプロジェクトに従事。それ以来Ruby一筋で、Ruby on Railsのプラグインやgemも開発。
2013年より、株式会社アジャイルウェアに所属。アジャイルな手法で、Ruby on Railsを使って企業向けシステムを構築する業務に従事。
Ruby関西所属。
Twitter:@spring_kuma、Facebook:山根 剛司
Copyright © ITmedia, Inc. All Rights Reserved.