ruby 1.9 は ruby 1.8 より eval が 3 倍くらい遅いというのは有名 (?) な話です。では、他の LL と比べてどうなんだろうと思ったので、比較をしてみました。
"1" を 100000 回 eval する
eval の前処理と後処理にかかる時間の比較。
ruby 1.8 (trunk) : 0.22 sec ruby 1.9 (trunk) : 0.82 sec perl 5.10 : 1.23 sec python 3.0rc1 : 1.83 sec php 5.3 alpha 2 : N/A (> 180 sec)
この結果だけみると、ruby 1.9 の eval はそれでも速い方に見えます *1 。
それはともかく PHP が激遅なんですが、どうも eval を繰り返し呼ぶと、なぜか呼んだ回数以上にどんどん遅くなる (O(n^3) くらい?) ので、意味のある速度比較ができません。ひょっとしたらやんごとなき事情があるのかもしれませんが、きっとバグだと思います。PHP さすがですね!
"1+1+1+ ...(100000 個)... +1" を eval する
すごく深い抽象構文木をパース・評価するのにかかる時間の比較。
ruby 1.8 (trunk) : N/A (SEGV) ruby 1.9 (trunk) : N/A (SEGV) perl 5.10 : 0.17 sec python 3.0rc1 : N/A (SEGV) php 5.3 alpha 2 : 0.36 sec
ruby もさすがですね! しかし python が落ちると驚きますね (ぼくの中で python は優等生なのです) 。原因はたぶん共通で、深さ 50000 の構文木を関数の再帰呼び出しでたどって、スタックオーバーフローしているものと思われます。
しかし比較にならないなあ。
関数定義 50000 個を eval する
何もしない関数の定義を 50000 個並べたものをパース・評価するのにかかる時間の比較。
ruby 1.8 (trunk) : 1.05 sec ruby 1.9 (trunk) : 2.81 sec perl 5.10 : 1.93 sec python 3.0rc1 : 4.51 sec php 5.3 alpha 2 : 1.01 sec
想像ですが、このくらいが現実を表しているんじゃないでしょうか。
- 抽象構文木をたどるだけの ruby 1.8 と php は速い *2
- 抽象構文木をたどるだけだけどパーサがややこしそうな perl はやや遅い *3
- バイトコードにコンパイルする ruby 1.9 と python は遅い
という感じ?ちゃんと調べてないですけどね。
まとめ
まあ、速度なんてどうでもいい話題な上に、eval の速度なんてかなりどうでもいいですよね。いいんです。
ベンチマークに使ったコード
#!/usr/bin/env ruby19 require "tempfile" RUBY18 = ["ruby 1.8 (trunk)", "ruby18"] RUBY19 = ["ruby 1.9 (trunk)", "ruby19"] PERL = ["perl 5.10 ", "./local/bin/perl"] PYTHON = ["python 3.0rc1 ", "./local/bin/python3.0"] PHP = ["php 5.3 alpha 2 ", "./local/bin/php"] def bench(header, *args) f = Tempfile.new("bench") yield f f.close time = (1..5).map do t = Time.now system(*args, f.path) (p $?; break []) if $? != 0 Time.now - t end.min f.unlink puts "%s : %.2f sec" % [header, time] if time end type = ARGV[0] || :all if type == "1" || type == :all puts "eval \"1\" (100000 times)" n = 100000 bench(*RUBY18) do |f| f.puts <<SRC i = 0 while i < #{ n } eval("1") i += 1 end SRC end bench(*RUBY19) do |f| f.puts <<SRC i = 0 while i < #{ n } eval("1") i += 1 end SRC end bench(*PERL) do |f| f.puts <<SRC for($i = 0; $i < #{ n }; $i++) { eval("1") } SRC end bench(*PYTHON) do |f| f.puts <<SRC for i in range(#{ n }): eval("1") SRC end #bench(*PHP) do |f| f.puts <<SRC #<?php #for($i = 0; $i < #{ n }; $i++) { # eval("1;"); #} #SRC #?> #end end if type == "2" || type == :all puts "eval 1+1+..(100000 times)..+1" s = "1+" * 100000 + "1" bench(*RUBY18) {|f| f.puts 'eval("' + s + '")' } bench(*RUBY19) {|f| f.puts 'eval("' + s + '")' } bench(*PERL ) {|f| f.puts 'eval("' + s + '")' } bench(*PYTHON) {|f| f.puts 'eval("' + s + '")' } bench(*PHP ) {|f| f.puts '<?php eval("' + s + ';"); ?>' } end if type == "3" || type == :all puts "define 50000 functions" n = 50000 bench(*RUBY18) do |f| f.puts "eval <<SRC" (1..n).each {|i| f.puts "def foo#{i};end" } f.puts "SRC" end bench(*RUBY19) do |f| f.puts "eval <<SRC" (1..n).each {|i| f.puts "def foo#{i};end" } f.puts "SRC" end bench(*PERL) do |f| f.puts "eval(<<SRC)" (1..n).each {|i| f.puts "sub foo#{i}() {}" } f.puts "SRC" end bench(*PYTHON) do |f| f.puts "exec(\"\"\"" (1..n).each {|i| f.puts "def foo#{i}(): pass" } f.puts "\"\"\")" end bench(*PHP) do |f| f.puts "<?php" f.puts "eval(\"" (1..n).each {|i| f.puts "function foo#{i}(){}" } f.puts "\");" f.puts "?>" end end
ついでに、PHP が eval を重ねる事に遅くなっていく様子。
$ ./local/bin/php -v PHP 5.3.0alpha2 (cli) (built: Oct 31 2008 19:55:08) Copyright (c) 1997-2008 The PHP Group Zend Engine v2.3.0, Copyright (c) 1998-2008 Zend Technologies $ ./local/bin/php -r ' $t = time() + (float)microtime(); while(1) { for($i = 0; $i < 10000; $i++) eval("1;"); $u = time() + (float)microtime(); print ($u - $t) . "\n"; $t = $u; } ' 0.58000898361206 1.6200242042542 3.3400499820709 12.870194911957