Windows での ruby の動作を速くしたい (2)
ということで、
の続きです。前回はファイルアクセスまわりに絞って動作検証しましたが、今回はビルド方法が違う ruby との比較がメインで。
ちなみに今回 Windows 上の Perl もとばっちりを食っています。
ruby-list のスレッドを読み返してみる
前回はファイルシステムを疑ったりファイル I/O を疑ったりしてましたが、ちゃんと読み返してみると的を外してる気がしてきました。
ここに書かれていることをまとめると、
- mswin32 版では 1.9 が 1.8 系より 3 倍程度遅い
- linux 版では 1.9 のほうが 2 倍程度速い
- mingw 版では 1.9 が 3 倍程度遅い(mswin32 と同様)
- バイナリモードで読み書きすることで 1.9 が遅い問題は解消する
という感じ。
profileをとっても、いまいち理解できませんでしたが、何となくファイルから
の読み込み、書き込みが遅くなっているような気がします。
とあるけど、あくまでも 1.9 系で遅くなっている原因が I/O 周りだと言ってるだけだったり。
こちらのスレッドからも気になる発言を引用。
WindowsのファイルシステムNTFSの問題かとも思い別のマシンでFAT32との
比較もしてみましたが差はありませんでした。
個人的な感覚としては、プロセス生成もWindowsは遅い気がしています。
改めてバイナリモードでもテストしてみましたが、差はありませんでした。
Cygwinでもテストしてみましたが、早くはありませんね。
また、Windows上にインストール(wbui.exeで)したUbuntuや、andLinuxでも
同様のテストをしてみましたが、NTFS上で実行した場合には遅く、NTFS上
に存在する仮想ディスク(ext3)では動作が速いので、正確にはファイルIOでは
なくて、ディレクトリ操作のスピードがWindowsでは遅いという事になるの
かも知れません。
ちゃんと読んでなかったんだけど、ext3 での検証は Ubuntu や andLinux で実施しただけなので、I/O が原因だとは言い切れない。もしかしたら I/O 関係なく遅い原因があるのかも?
MinGW 版も試してみる
上記のスレッドでは議論の対象外ですし、実行している環境もまちまちなのでなんとも言えないのですが、1.8.x 系での実行時間はおおよそ
とのことで、mswin32 の動作が遅い現象が見て取れます。mingw 版で動作させたら解決しないかな?という期待が少し持てます。
ビルド方法は
を参考に・・・と思ったけど、
- Rubyをmingwでmakeする - Rubyで何か作るページ(※現在はリンク切れ)
にファイルがあったので、こちらを使ってみました。動作速度の詳細は後述しますが、mswin32 版と大差ありませんでした・・・。
いろんなビルドで速度比較してみる
MinGW 版との比較だけではなく、Ubuntu 上の ruby 等と動作を比較してみることにします。
環境
使用する PC は Thinkpad X61(Intel Core2 Duo T8100 @ 2.10GHz、3.00 GB RAM、SSD(S64GSSD25-M)使用)で、Windows 7 Ultimate 32 bit が稼働中。ここに wubi で Ubuntu を入れて、そこでインストールした ruby と速度を比較します。ついでに OSx86(hackintosh)も入れっぱなしだったので、そこでも動作確認してみます。こちらはバージョンも大きく違ったりしますので、参考値ということで。
詳細なバージョンは下記のとおりです。
- Windows(mswin32)
- ruby 1.8.7 (2010-01-10 patchlevel 249) [i386-mswin32]
- Windows(mingw32)
- ruby 1.8.7 (2008-08-11 patchlevel 72) [i386-mingw32]
- Ubuntu
- ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux]
- OSX86
- ruby 1.8.6 (2008-03-03 patchlevel 114) [universal-darwin9.0]
Windows 環境にはアンチウィルスソフト等も入っているので、全部入りの状態とセーフモードの両方で計測しています。
使用スクリプト
元の ML で検証に使われていたスクリプト
を使って、ファイル入出力周りの所要時間を計測します。
ファイル入出力以外の処理も遅い疑いがあるので、単純な再帰呼び出しの所要時間もついでに計測します。
に載っていた処理を拝借して、tak(12, 6, 0) を計算しました。
結果
それぞれ
- (1) print strings to file(ファイル出力)
- (2) read from file, and regexp match, and print to file(ファイル入出力+マッチング)
- (3) tak(12, 6, 0)(再帰呼び出し)
の所要時間(秒)です。
- | (1) | (2) | (3) | 備考 |
---|---|---|---|---|
mswin32 | 1.40(0.90) | 9.55(9.17) | 9.58(10.08) | 括弧内はセーフモード時 |
mingw32 | 1.17(0.77) | 8.25(6.97) | 6.33( 6.69) | 括弧内はセーフモード時 |
ubuntu | 0.71 | 5.95 | 10.11 | |
osx86 | 0.87 | 6.47 | 21.32 |
ということで、
- (1) のファイル出力については Windows が 2 倍程度遅いように見えるが、セーフモード時には大差ないのでアンチウィルスソフト等の影響が大きい
- (2) では (1) と同じような傾向があるが、mswin32 ではセーフモードにしても Ubuntu との差があまり埋まっていない
- (3) のように入出力を伴わないケースでは mingw32 > mswin32 = ubuntu のような状態。セーフモードのほうが動作が遅いのは???
という感じです。
前回リンクを貼った id:ashel のベンチマークによると、ファイルを探す処理に時間がかかっているとのことなので、この結果はそれなりに納得です。
ついでに Perl も計測してみる
今回「Windows では動作が遅い」というのが気になって調査しているわけですが、こんな現象は Ruby 以外では聞いたことないような気がします。試しに Perl でも似たようなスクリプトを用意して計測してみました。
これでもし Windows と Ubuntu で動作速度に差異がなかったら、Windows 上の ruby も改善の余地があるということになるかも、と期待しつつ。
環境
結果
それぞれ
- (1) print strings to file(ファイル出力)
- (2) read from file, and regexp match, and print to file(ファイル入出力+マッチング)
- (3) tak(12, 6, 0)(再帰呼び出し)
の所要時間(秒)です。
- | (1) | (2) | (3) | 備考 |
---|---|---|---|---|
mswin32 | 0.35(0.37) | 8.21(7.89) | 221.77(216.43) | 括弧内はセーフモード時 |
ubuntu | 0.21 | 3.96 | 7.96 | |
osx86 | 0.51 | 4.65 | 14.48 |
・・・うん。予想外の結果で困りました。
(1) でファイル入出力の差が 1.5 倍程度ありますが、そんなのは誤差の範囲で。Ubuntu で 8 秒で終わるたらいまわしが Windows 上で 216 秒ってどういうこと?今まで Windows 上の Perl で Google Code Jam に参加して「時間内に計算終わらない〜」と苦しんだりして、「やっぱり LL だけじゃダメかも・・・」と方向転換してたのに・・・Perl 側の問題だった?
とりあえず Ruby との比較という意味では、何の参考にもなりませんでした。
Windows での ruby のコンパイル
ファイルアクセス周りを疑ってた頃に試したことなんですけど、ついでに書いておきます。
ruby のソースコードをちょっといじって、ファイルアクセスの部分を仮想ディスクへの読み書きに変えたりすることで高速化できないかなー?と妄想して、ruby のコンパイルをやってみました。
手元にあった Visual C++ 2005 Express Edition を使ったわけですが、
にあるように、byacc をダウンロードしておく必要があります。
コンパイル時に「revision.h: No such file or directory」みたいなエラーが出たのでググってみると、自分でファイルを作る必要があるみたいですね。
また、コンパイル中に
ruby.exe - エントリ ポイントが見つかりません プロシージャ エントリ ポイント rb_thread_status がダイナミック リンク ライブラリ msvcrt-ruby18.dll から見つかりませんでした。
というエラーが発生することがありました。これは ruby\lib\ruby\1.8\i386-mswin32\thread.so が rb_thread_status を探すようになっているのが原因のようですが、
によると、
One Click Ruby Installer版ではなく ActiveScriptRubyを使う
ことで解決するとかなんとか?なので、とりあえず ActiveScriptRubyから thread.so を拝借して動作させました。
コンパイルしたいソースと同じバージョンの ActiveScriptRuby から持ってこないと、
ruby/lib/ruby/1.8/i386-mswin32/rbconfig.rb:7: ruby lib version (1.8.6) doesn't match executable version (1.8.7) (RuntimeError)
みたいなエラーになるので注意です。
元々使っていたバージョンが
ruby 1.8.6 (2007-09-24 patchlevel 111) [i386-mswin32]
なので、このバージョン持ってきて・・・
svn co http://svn.ruby-lang.org/repos/ruby/tags/v1_8_6_111 mkdir build cd build ..\win32\configure.bat --with-winsock2 nmake
こんな感じでコンパイル→できあがった ruby.exe と msvcrt-ruby18.dll で InstantRais の配下のものを置き換えて起動、と。
すると・・・もともと 2〜3 秒で終わっていたページ表示が 7〜8 秒かかるようになってしまいましたorz
これじゃ検証にならない、ということでこの道はあきらめたのでした。
ただ、単純なスクリプトでは自前ビルドの ruby でもそんなに速度が変わらなかったりして、かなり不思議な挙動になっていました。Windows で C をコンパイルしたときってたまにこういう現象があって、Hello, world レベルのプログラムを起動するのに 1 秒くらいかかることがあるんだけど、何が原因なんだろう・・・。
その他試したかったこととか
- ちゃんと Windows/Linus 両方の環境でプロファイラを使って計測して、どこの処理が Windows で遅くなっているか確認したかったんだけど、Windows 上で使えるフリーのプロファイラが見当たらなくて諦めた。(Dev Partner Profile Community Edition ってのがあったらしいんだけど、今では公開されていない模様。)
- Rubinius とやらの動作も確認したかったけど、Windows で動作しないようだから特に何もせず。
私は開発効率にこだわりがある人間なので、今までは「ネイティブ動作させて少しでも高速に動作させよう」というスタンスだったんですが・・・Windows 上での動作がどうしても遅くなってしまうというのなら、次回以降 Ruby の開発では VM 上で行うのもやむなしですね。
これを口実に経費で MacBook を買う手もあるかもしれないけど・・・開発者の人数が読めないし、現実的じゃないよなぁ・・・。
実際のスクリプト
最後に、計測に使ったスクリプトを貼っておきます。計測に影響でそうな処理があればご指摘いただけると。
bench-ruby.rb - Ruby のファイル I/O 等
t_o = Time.new str = "abcdef\nghijklmno\npqrstu\nvwxyz\n0123456879" puts "print string to file" t = Time.new open("test.txt","w") do |file| 1.upto(500000) do |n| file.print str,"\n" end end print Time.new-t,"\n\n" puts "read from file ,and regexp match ,and print to file" t = Time.new reg = Regexp.new("abcdef|nvwxyz|5687") open("test2.txt","w") do |file2| open("test.txt") do |file3| file3.each_line do |line| line =~ reg file2.print line end end end print Time.new-t,"\n\n" File.unlink("test.txt") File.unlink("test2.txt") puts "Finished,press any key" key = gets
tak-ruby.rb - Ruby のたらいまわし
puts "calculating tak" t = Time.new def tak(x, y, z) if x <= y y else tak(tak(x-1, y, z), tak(y-1, z, x), tak(z-1, x, y)) end end x, y, z = ARGV.map{|i| Integer(i) } tak = tak(x, y, z) print Time.new-t,"\n\n" puts "tak(#{x}, #{y}, #{z}) = #{tak}"
bench-perl.pl - Perl のファイル I/O 等
use strict; use warnings; use Time::HiRes; my $str = "abcdef\nghijklmno\npqrstu\nvwxyz\n0123456879"; print "print string to file\n"; my $t = Time::HiRes::time(); open my $out, '>', "test.txt" or die "$!"; for (my $i = 1; $i <= 500000; $i++) { print $out "$str\n"; } close $out; print+ (Time::HiRes::time() - $t) . "\n\n"; print "read from file ,and regexp match ,and print to file\n"; $t = Time::HiRes::time(); my $re = qr/abcdef|nvwxyz|5687/; open my $out2, '>', "test2.txt" or die "$!"; open my $in, '<', "test.txt" or die "$!"; my $line; while ($line = <$in>, defined $line) { $line =~ $re; print $out2 $line; } close $in; close $out2; print+ (Time::HiRes::time() - $t) . "\n\n"; unlink("test.txt"); unlink("test2.txt"); print "Finished,press any key\n"; my $key = <STDIN>;
tak-perl.pl - Perl のたらいまわし
use strict; use warnings; use Time::HiRes; print "calculating tak\n"; my $t = Time::HiRes::time(); sub tak { my ($x, $y, $z) = @_; if ($x <= $y) { return $y; } else { return tak( tak($x - 1, $y, $z), tak($y - 1, $z, $x), tak($z - 1, $x, $y) ); } } my ($x, $y, $z) = map { int $_ } @ARGV; my $tak = tak($x, $y, $z); print+ (Time::HiRes::time() - $t) . "\n\n"; print "tak($x, $y, $z) = $tak\n";