Ruby の Dir.glob にブロックを渡すと、見つかった順に処理してくれる?
結論
Dir.globは見つけた順にブロックを評価するのかと思っていた。そんなことはなかった。
Dir.glob("**/*") {|file| ... }
は、ブロックに最初の1ファイル目を渡す前に、全ファイルを探す。
確認
環境は Mac + Ruby 1.8.7, 1.9.3。
確認に使ったスクリプト。
ディレクトリ2階層の下に計10万ファイルを作成し(create_many_files.rb)、1ファイル目を処理するまでにかかる時間と、全体を処理し終わる時間を
Dir.glob("**/*") {|file| ...} Dir.glob("**/*").to_a.each {|file| ...}
の2つで比較した(test.rb)。
$ ruby dirs.rb 1.9.3 ---- block ---- first: 1.8421268463134766 last: 1.8548080921173096 200103 ---- each ---- first: 1.8584198951721191 last: 1.8712561130523682 200103 ---- block ---- first: 1.8612079620361328 last: 1.8742032051086426 200103 ---- each ---- first: 1.8329567909240723 last: 1.8456907272338867 200103 : $ rvm use 1.8.7 $ ruby dirs.rb 1.8.7 ---- block ---- first: 1.71229195594788 last: 1.75075793266296 200103 ---- each ---- first: 1.73645281791687 last: 1.77572989463806 200103 ---- block ---- first: 1.73904895782471 last: 1.77682209014893 200103 ---- each ---- first: 1.75206685066223 last: 1.79086184501648 200103
each でも ブロックを渡しても、差が見られない。
関係ないけど 1.9.3 より 1.8.7 のほうがちょっと速かった。
確認その2
dtraceを使って、プロセスがアクセスしたファイルを出力した。
dtrace oneliners より
# Files opened by process, dtrace -n 'syscall::open*:entry { printf("%s %s",execname,copyinstr(arg0)); }'
最初の処理の前に何秒かsleepsするようにした。
$ test.rb 5 # 5秒スリープ
dtraceで、スリープする前に全ファイルが出力されるのを確認できる。ブロックでもeachでも同じだった。
まとめ
Dir.glob() {} は全ファイル探してからブロックを実行する。
見つけたファイルから順にブロックを評価するライブラリがどこかにはありそうだが、見つけられなかった。