先日、以下の記事を公開しました。
Rubyで配列からハッシュを作る際に、どの方法が一番パフォーマンスが良いのかを計測したのですが、過去にJavaScriptで似たような計測をした時はシンプルなfor文が最速だったので、Rubyでも同じような結果になると思っていたらfor in
が最も遅く、each関数が最も速いという結果になりました。
一体、なぜこのような結果になるのか。Rubyの深い部分まで把握する必要がありそうだと判断し、前回の記事では調査を行わなかったのですが、今回はその謎に迫ってみたいと思います。
そもそもRubyにはfor文がない
この記事を書く前までRubyには他言語と同様にfor文が定義されているものだと思っていましたが、それが勘違いであることに気づきました。Rubyでは繰り返し処理を記述したい場合、while
やeach関数を使うのが一般的です。例えば、JavaScriptでは10回、文字列を出力する際に以下のように書きます。
for (let i = 0; i < 10; i++) { console.log('hello!'); }
よくある一般的なfor文かなと思います。それに対して、Rubyでは一例として以下のように書きます。
for x in 1..10 do puts "hello!" end
前回の計測でeach関数よりも遅いと判明したfor in
です。
パッと見た感じ、どちらも同じ一般的な繰り返し処理に見えますが、Rubyのfor in
はJavaScriptで実装されているfor文とは似て非なるものです。一体、何が違うのでしょうか。
for inは糖衣構文
なんとRubyのfor in
は糖衣構文であり、内部でeach関数に近しい処理を実行しているそうです。
先ほどはRubyの組み込みのループプリミティブはwhileとuntilだけだと言いました。
ではこの「for」というものは何でしょうか。実際には、forはほぼ糖衣構文といえるものです。
Programming Ruby: The Pragmatic Programmer's GuideのFor...inより
つまりfor in
を呼び出すと内部ではeach関数に近しい処理に変換されているということが分かりました。
# list.each { |x| puts x } に近しいものに変換される for x in [1,2,3] do puts x end
なるほど...確かにそれならfor in
がeach関数よりも遅いという説明がつきますね。
つまりRubyにはfor文は実装されていないということになります。ただ、ループのプリミティブな定義はwhile
とuntil
だけとありましたが、each関数はどうなのでしょうか。内部でwhile
を呼び出していたりするのでしょうか。
array.cを見てみる
いよいよ最深部へ...。RubyのC言語実装のコードへやってきました。
each関数が実装されているのはarray.cというファイルだそうです。
VALUE rb_ary_each(VALUE ary) { long i; ary_verify(ary); RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); for (i=0; i<RARRAY_LEN(ary); i++) { rb_yield(RARRAY_AREF(ary, i)); } return ary; }
まずeach関数の実装がC言語のファイルにあるということで、each関数が内部でRubyのwhile
もしくはuntil
を呼び出していないことが分かりました。each関数のパフォーマンスが良いのは内部でC言語のコードを呼び出して実行しているからというのが理由の一つなのでしょうね。
この実装の詳細については分かりかねますが、明らかなのはC言語のfor文を使って、配列の処理を行なっているという点です。
まとめ
for in
はeach関数と比べるとパフォーマンスで劣る- Rubyにはプリミティブなfor文が用意されておらず、
while
とuntil
のみ - なんと
for in
は糖衣構文であり、内部でeach関数に近しい処理を行なっている - each関数はC言語で実装されており、内部ではC言語のfor文が記述されている
少しでも「ええな〜」と思ったらイイネ!・シェア!・はてなブックマークを頂けると励みになります。