読者です 読者をやめる 読者になる 読者になる

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() {} は全ファイル探してからブロックを実行する。

見つけたファイルから順にブロックを評価するライブラリがどこかにはありそうだが、見つけられなかった。

余談

Dir.glob の順序は保証されない。