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 はまだこのへんに最適化の余地があるのかな。