Ruby 2.0(開発版)に入ったEnumerable::Lazyを試してみた
ゆくゆくは Ruby 2.0に入るようだ。
今回、Ruby 2.0の開発版を実際に動かしてみた。
Ruby 2.0 Enumerable::Lazy | Railsware blog
(うちの猫がLazyな時の写真)
Lazyが無いとどうなるか
次の例ように、ファイルから行単位で処理するようなRubyプログラムがあるとする。 bashで書くならば、 “grep ruby test.txt | head -5” のような処理。
File.open( 'test.txt' ) do |f|
arr = f.map(&:chomp).grep(/ruby/).take( 5 )
arr.each{ |x| puts x }
end
一般的に、読みやすいコードを心掛けると上のように全行をメモリに読み込んで処理するように書くだろう。 mapとかeachを使ってメソッドチェーンを多用すれば簡潔に書けるからだ。
ただ、メソッドチェーンの度に新しいArrayが確保されるため、test.txtが大きいとメモリを大量に消費する。 Lazy(遅延評価)はこれを綺麗に解決してくれる。
Ruby 2.0をインストールする方法
Ruby 2.0をビルドしたいなら、githubからcloneするのが簡単だ。 masterブランチがRuby 2.0の開発版になっているようだ。
git clone https://github.com/ruby/ruby.git
Cloning into ruby...
remote: Counting objects: 222847, done.
remote: Compressing objects: 100% (44673/44673), done.
Receiving objects: 92% (206057/222847), 64.40 MiB | 317 KiB/s pwd
remote: Total 222847 (delta 177021), reused 220708 (delta 176422)
Receiving objects: 100% (222847/222847), 76.22 MiB | 310 KiB/s, done.
Resolving deltas: 100% (177021/177021), done.
~/work/github $ cd ruby/
~/work/github/ruby $ ls Makefile*
Makefile.in
~/work/github/ruby $ autoconf
~/work/github/ruby $ bash ./configure
checking build system type... x86_64-unknown-linux-gnu
checking host system type... x86_64-unknown-linux-gnu
.
.
.ext/include/x86_64-linux/ruby/config.h updated
verconf.h updated
ruby library version = 2.0.0
configure: creating ./config.status
config.status: creating Makefile
config.status: creating ruby-2.0.pc
~/work/github/ruby $ make dist
ruby ./tool/make-snapshot tmp
Exporting trunk@35198
Exported revision 35198.
take a breath, and go ahead
creating configure... done
creating prerequisites...
.
.
prerequisites done
creating bzip tarball... /home/kiyoka/work/github/ruby/tmp/ruby-2.0.0-r35198.tar.bz2 done
creating gzip tarball... /home/kiyoka/work/github/ruby/tmp/ruby-2.0.0-r35198.tar.gz done
creating zip archive... /home/kiyoka/work/github/ruby/tmp/ruby-2.0.0-r35198.zip failed
* /home/kiyoka/work/github/ruby/tmp/ruby-2.0.0-r35198.tar.bz2
SIZE: 9913268 bytes
MD5: 943937f8635458fedfc271f97e6133c9
SHA256: ed5850f393db8ec39568fcd9446566ebcdabe4584c0f8c7a827e21e0bd0a9e3a
* /home/kiyoka/work/github/ruby/tmp/ruby-2.0.0-r35198.tar.gz
SIZE: 12561052 bytes
MD5: 60bb6bdf08dcdbf424cd757b517ded75
SHA256: 898aa4e3222d5585621112dd8c277d3e565a130540f2ea8a5bd4b0d70c7ad334
~/work/github/ruby $
できた tmp/*.tar.gz をオフィシャルリリースされた ruby-1.9.3-p125 の tar.gz と同様にビルド、インストールできる。 ※ ちなみに、私は stowというツールでいろんなRuby環境をスイッチできるようにしている。
Lazyの効果
実際に大きなサイズのtest.txtを用意して試してみた。
$ du -sh test.txt
198M test.txt
* Lazyなし
$ ruby ./lazy_sample.rb
*without Lazy*
bioruby
eruby
fxruby
hruby
hruby's
=> VmPeak: 1282188 kB (1252.1MB)
* Lazyあり
$ ruby ./lazy_sample.rb lazy
*with Lazy*
bioruby
eruby
fxruby
hruby
hruby's
=> VmPeak: 26280 kB (25.6MB)
プロセスがどこまでも肥大したか(ピーク)を調べると、lazyを使ったバージョンは25.6MByteしかメモリを消費しないことがわかった。 “.lazy.” の6文字を追加しただけでこれだけの効果がある。素晴らしい。
実験したソースコード
lazy_sample.rb sample for Enumerable::Lazy of Ruby 2.0 — Gist
感想
Rubyに遅延評価が入るとは思ってなかったのでこれは嬉しい。 私が作っているLisp処理系の[Nendo]はRubyとのインテグレーション済みなので、[Nendo]のmapやfor-eachにEnumerable::Lazy型を渡せば同様にLazyに処理できるはず。(今度実験してみよう) 実は、普段[Nendo]で500MByteクラスのテキストファイルを処理することがよくある。その度に、メモリ消費量を削減するためにfor-eachで1行づつ処理するループをコーディングしていた。 もうその必要が無くなるだろう。Ruby 2.0が正式リリースされるのが本当に楽しみになったぞ、これは。