Ruby Freaks Lounge

第1回 Ruby1.9の新機能ひとめぐり(前編):YARV,Fiber,配列処理の強化

この記事を読むのに必要な時間:およそ 3 分

Ruby1.9.1リリース

2009年1月,Ruby1.9.1がリリースされました。Ruby1.9系列初の安定版とされるリリースです。Ruby1.9系列は,従来のRuby1.8系列と比べて,次のような特徴を持っています。

  • 高速化や省メモリ化といった最適化
  • 多言語化をはじめとするさまざまな機能拡張
  • 文法の改良・拡張
  • その他,気の利いた機能

なかでも,「YARVによる高速化」「M17N対応(多言語化)」は有名で,聞いたことのある人も多いと思います。

しかしRuby1.9には,他にも様々な改善や新機能が数多くあります。今回執筆を担当する,第1回,第3回,第5回では,あまり知られていない新機能にもスポットをあてつつ,Rubyの新機能を駆け足で紹介したいと思います。

YARV(Yet Another Ruby VM)による高速化

Ruby1.9のインタプリタは,笹田耕一氏が開発している仮想機械YARV(Yet Another Ruby VM)がコアとなりました。まずRubyプログラムをYARVバイトコードにコンパイルし,そのバイトコードをYARV上で実行する,という2段階になっています。

YARVのおかげで,1.9は1.8に比べて実行速度が大きく向上しました。特に統計処理,物理計算,人工知能のような,計算処理がボトルネックとなるプログラムでは多大な恩恵を得られます。

ただし,現在の1.8のメインターゲットであるWebアプリでは,計算ではなく通信やデータベースアクセスがボトルネックになることが多いため,それほどの効果は期待できません。さらに,コンパイルフェーズが増えるために,evalやrequireなど一部のメソッドは1.8よりも遅くなることがあります(具体的にはgemやrakeといったコマンドラインツール,FastCGIなどを使わない単純なCGIなどが影響を受けます)。

YARVの本当の成果は速度改善ではなく,Rubyのコアを近代的なVM実装に置き換えたことであるとも言われます。YARVが更なる新機能や最適化を行っていくための基盤となり,Ruby2.0に向けて開発が加速していくことを期待しましょう。

M17N(多言語化)対応

これについては,M17Nの開発担当である成瀬さんからの解説が予定されていますので,省略します。お楽しみに。

Fiberの導入

1.9で導入された,新しい制御構造です。他の言語では,コルーチン,マイクロスレッド,軽量スレッドなどと呼ばれることもあります。Fiberを使うと,プログラムの実行を中断したり,再開したりすることができます。

コード1 Fiberの使い方

# Fiberを作る (作るだけでは実行されない)
fiber = Fiber.new do
  p "fiber 1"
  Fiber.yield # 実行を中断する
  p "fiber 2"
end

p "main 1"
fiber.resume # fiberの実行を開始し,中断されたところで戻ってくる
p "main 2"
fiber.resume # fiberの実行を再開し,最後まで実行して戻ってくる
p "main 3"

図1 コード1の実行結果

"main 1"
"fiber 1"
"main 2"
"fiber 2"
"main 3"

Fiberを使うと,複数の処理を少しずつ交互に実行するようなプログラムを直感的に書けるようになります。よく言われる例では,シューティングゲームなどで,敵や弾をそれぞれ1フレームずつ動かす処理です。また,アドベンチャーゲームで画面描画処理とシナリオ進行管理を交互に行うような場合も考えられます。

実際にFiberが用いられている例としては,データベースへの複数のトランザクションを管理するNeverBlockというライブラリがあります。NeverBlockはFiberを使い,「直感的なトランザクション記述」「各トランザクションを交互に(非同期に)実行することによる効率性」を両立しています。

配列処理の強化

1.9では,1.8系列に比べてイテレータなどの配列処理が大幅に強化されています。

Enumeratorによってイテレータの組み合わせが容易に

Array#eachなどに代表されるイテレータをブロックなしで呼び出すことで,Enumeratorオブジェクトが得られるようになりました。Enumeratorオブジェクトとは,すごく簡単に言うと「イテレータが列挙する要素を順に入れた配列のようなもの」です。Enumeratorのおかげで,イテレータを組み合わせやすくなりました。

コード2 Enumeratorを用いてイテレータを組み合わせる

ary = [1, 2, 1, 3, 3, 1, 4, 2, 2]

# 1.8の場合
result = nil
ary.each_cons(2) do |x, y|
  if x == y
    result = [x, y]
    break
  end
end
p result #=> [3, 3]

# 1.9の場合
result = ary.each_cons(2).find {|x, y| x == y }
p result #=> [3, 3]

これはeach_cons(n)(連続するn個の要素を組にして列挙するイテレータ)を使って,最初に登場する「同じ値が2連続になっている組」を見つける例です。1.8ではeach_cons(2)が列挙する組から条件を満たすものを自力で探していますが,1.9ではEnumerable#findを組み合わせて使うことができ,記述が簡潔になりました。

ここで,「1.8は途中でbreakしているが,1.9のコードは最後まで列挙しているので非効率ではないか」と思った方がいるかも知れません。しかし心配は要りません。Enumeratorが「配列のようなもの」であって「配列」でないのはその点です。Enumeratorは,各要素が実際に必要になるまで元のイテレートの実行を延期します。この例では,each_consの呼び出し自体はイテレートをせずEnumeratorを返すだけです。findが呼ばれることで初めてイテレートが行われます。さらにfindは[3,3]を見つけた時点でイテレートをとめるため,each_consのイテレートもそこで止まります。要するに,余計な列挙をしたり巨大な配列を確保したりはしないということです。

おそらく最もよく組み合わせるイテレータはwith_indexでしょう。これによって,任意のイテレータでインデックスを自力管理する必要がなくなりました。

コード3 with_index の使用例

# map と with_index を組み合わせる例
p ["dog", "cat", "bear"].map.with_index {|s, i| "#{ s } #{ i }" }
  #=> ["dog 0", "cat 1", "bear 2"]

著者は1.9の新機能の中では,この機能を特に使います。

著者プロフィール

遠藤侑介(えんどうゆうすけ)

某電機メーカーで研究職勤務。個人活動として,2008年1月よりRubyの開発に参加。テストを書いたり,バグをとったり,バグを入れたりしています。好きな言語はRubyとHaskellとOCaml 。使う言語はRubyとC 。

URLhttp://d.hatena.ne.jp/ku-ma-me/

コメント

コメントの記入