Ruby 1.9 の新機能もうひとめぐり (前編)

ref: Ruby Freak Lounge 第1回 Ruby1.9の新機能ひとめぐり(前編):YARV,Fiber,配列処理の強化
の補足など。

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

いきなり本編とあまり関係ないんだけど、高速化のまめ知識をひとつ。
YARV では while や if のようなプリミティブの構文が最適化されています (というか、Ruby の中で数少なく最適化の余地があったところ) 。そのため、1.9 では C メソッドやブロックを呼び出すより while を使ったほうが速いです。

# 1.8 で 13 秒、1.9 で 2.4 秒
n = 0
while n < 50000000
  n += 1
end

# 1.8 で 6.5 秒、1.9 で 5.0 秒
50000000.times {|n| }

# 1.8 で 4.3 秒、1.9 で 4.8 秒
50000000.times { }

# 1.8 で 6.7 秒、1.9 で 4.9 秒
(0..50000000).each {|n| }

# 1.8 で 4.4 秒、1.9 で 4.9 秒
(0..50000000).each { }

# 1.8 で 4.5 秒、1.9 で 5.4 秒
for n in 0..50000000 do
end

*1
速さの関係が「1.8 の while」<「1.8 のブロック」≒「1.9 のブロック」<「1.9 の while」という感じなことがわかります。とはいえ、while ばかり使うと可読性が落ちますので、基本的にはブロックを使うべきです。使いましょう。使ってください。
あと、1.9 に付属しているマイクロベンチマークは、多くの箇所が while で書かれていて、少し 1.8 が不利になるように仕向けられています。ここだけの話。これはデマでした。while の分は差し引かれてました。ささださんごめんなさい。

Fiber の導入

Fiber ローカルな変数がほしいときは、Thread#[] を使うといいです。

Thread.current[:foo] = "main"

Fiber.new do
  Thread.current[:foo] = "fiber"
  p Thread.current[:foo] #=> "fiber"
end.resume

p Thread.current[:foo] #=> "main"

つまり Thread#[] はスレッドローカルだけでなく Fiber ローカルでもあるということ。ちょっと気持ち悪いけど、そういうものらしいです。知らないとはまりそうですね。

配列処理の強化

自分でイテレータを定義するときは、ブロック省略時に Enumerator を返すおまじないを最初のあたりに入れるといいです。

class Foo
  # イテレータのつもり
  def each_foo
    # おまじない
    return to_enum(__method__) unless block_given?

    (0..50000000).each {|x| yield }
  end
end

foo = Foo.new

# Enumerator を経由すると Enumerable#find が使える
n = foo.each_foo.find {|m| m == 10000000 }

# ただし、こっちの方が効率はよい (上が 26 秒、下は 19 秒)
n = nil; foo.each_foo {|m| (n = m; break) if m == 10000000 }

*1:これを書いていて気がついたけど、1.8 ではブロック引数を省略すると速くなりますね。1.9 はまだこのへんに最適化の余地があるのかな。