!!!wxRuby + Exerb + UPX 2009 この記事は[[wxRuby + Exerb 2009]]の派生記事だ。この記事のフォーカスは''UPX''にあるので、wxRubyとExerbについて知りたい方はそちらの記事を参照のこと。以下、そちらの記事の知識があることを前提として書いている。 !!.exeをより小さくしたい [[wxRuby + Exerb 2009]]のおまけで示したとおり、Hello! World!レベルの.exeを単純にUPXで圧縮しても、2.4MiB程度にはなってしまう。複数の.exeを配布したい場合、このサイズはバカにならない。なんとかしてもう少し小さくできないだろうか。 ''注:複数の.exeを作成しない場合でもこの節を最後まで読んで見て欲しい。'' !ExerbのコアをDLLで利用する Exerbでは、その機能コアをDLLで使用することができる。 複数の.exeを作成する場合、コア部分は皆同じなので、このDLLを1つ同梱するようにすれば''合計サイズを節約''することが可能だ。 コアにDLLを使用する場合、exerbコマンドで以下のようにする。 exerb -c cuirt app.exy または、実行ファイル(.exe)実行時にコマンドプロンプトを表示させたくなければ exerb -c guirt app.exy でOKだ。 これによって完成した実行ファイルは圧縮前で488KiBほどサイズが小さくなり、 C:\Program Files\ruby-1.8\share\exerb にある''exerb50.dll''(圧縮前で584KiB)が実行時に必要になる。 しかし、wxRubyをタイトルに標榜するこの記事では、これぐらいでは焼け石に水だ。 wxruby2.soが9852KiBとバカでかいので、むしろこっちをなんとかしなければならない。 !wxRubyのコアをDLLで利用する wxRubyのコアを.exeに含めずDLLで利用するためには、mkexyの完了後、作成されたレシピファイル(.exyファイル)を開いて wxruby2.so: file: C:/PROGRA~1/ruby-1.8/lib/ruby/site_ruby/1.8/wxruby2.so type: extension-library とある部分を # wxruby2.so: # file: C:/PROGRA~1/ruby-1.8/lib/ruby/site_ruby/1.8/wxruby2.so # type: extension-library とコメントアウトし、後は同じようにexerbコマンドを実行すればよい。この場合には、実行時に上記exerb50.dllの他にwxruby2.soが必要になる。 これによって上記10.3MiBあった実行ファイルは208KiBまでになった。 さらにUPXを使ってみよう。UPXのオプションは"-5 --lzma"で.exe本体が52KiBになった。 exerb50.dllとwxruby2.soも圧縮可能で、それぞれ238KiBと1749KiBまで小さくなった。 .exe、exerb50.dll、wxruby2.soの合計サイズは''約2039KiB''になる。'''…ちょっと待て'''よ。 !分離圧縮のススメ 筆者は[[元記事|wxRuby + Exerb 2009]]の「おまけ1」で''「--lzmaオプションを使用すると2461KiB」''と書いた。 アプリケーションはこの時と全く同じものだが、ExerbとwxRubyのコアをDLLで使用し、それぞれ別にUPXで圧縮しただけで''約422KiBも小さく''なっている。 これはどういうことだろう。 ここで、ふと思い出した。UPXで"--lzma"オプションを指定して実行ファイルを圧縮した場合、同じアルゴリズムを使用する(というよりこちらが本家である)7-zipで圧縮した場合よりもサイズが小さくなることがある。 これは恐らく、UPXが''実行ファイル(.exe)に特化し命令コードを整理する前処理''を行っており、これが上手く効くケースでは7-zipよりも効率よく圧縮できることがその原因だ。 wxruby2.soを実行ファイル(.exe)に含めた場合、wxruby2.soは「データ」として含まれることになる。 この場合UPXはここを「命令コード」とはみなさないため、汎用的な圧縮しかなされないのだろう。 wxruby2.soをUPXで圧縮した場合、その実体はDLLであり「命令コード」として圧縮されるため、UPXの前処理が効いてより小さくなる可能性がある。 実際にwxruby2.soを7-zipで圧縮すると、2224KiBになり、UPXで圧縮した1749KiBとは約475KiBの差がある。細かな事情による誤差を考えれば、上記約422KiBの差は''wxruby2.soにUPXの前処理が効いた結果''と言ってよいだろう。 なお、蛇足だが7-zipにも実行ファイルを意識した前処理(BCJ)は存在する。ただし、7-zipは標準でこの処理を拡張子.soのファイルに適用しない。wxruby2.soをwxruby2_so.dllとリネームすると、BCJが効くようになり標準圧縮で1904KiB、超圧縮で1842KiBまで縮むが、上記の通り''wxruby2.soにはUPXの前処理の方が効く''ようだ。 !!結論 wxRuby2を使用するRubyスクリプトをExerbで.exe化する場合には、作成する''実行ファイル(.exe)が1つだけの場合でも、wxruby2.soは外に出し別途UPXで圧縮''しよう。 このほうがwxRuby2.soにUPX圧縮が良く効くため、トータルでサイズが小さくなる。 実行ファイル(.exe)が複数の場合にはExerbのコアもDLL版を使用し、exerb50.dllを別途UPXで圧縮するとよい。 実行ファイルが1つだけの場合にExerbのコアをDLL版で使用しないのは、UPXの展開コードが.exeや.dll/.soのファイルごとについてしまい、''重複してしまい冗長''であるためだ。DLLに分離した場合のDLL読み込みコードも省略されるので、元のデータ自体が小さいもので済む。 実際、DLL版で完成した.exeとexerb50.dllをUPXにかけた合計サイズは約290KiBだが、スタティックリンク版のExerbコアを使用してUPXで圧縮した場合には251KiBとなり、約31KiB小さい。 なお、Exerbのコア自体はスタティック版でもUPXの前処理の対象になるはずだが、後述のサイズ一覧にある7-zipでの圧縮結果を見る限りあまり効果が現れない(相性がよくない)ようだ。 !注意点 この方法を使用する場合に、1つ注意点がある。 Exerbの制限かUPXの制限か不明だが、''実行ファイル(.exe)のファイル名が長いとrequireに失敗''し、Ruby上の例外を発生することがあるようだ。 試した限りでは、"12345678901234.exe"だと失敗するが、"1234567890123.exe"では失敗しない。 ファイル名のベース部分が13byteまでという制限なのか、ファイル名全体で17byteまでという制限なのかは不明だ。 少し詳細を追求しておくと、上位フォルダの名前を変更し絶対パス長を長くしてもエラーとはならないため、ファイル名のみにかかる制限だと思われる。 また、"123456789012猫.exe"では失敗するが、"12345678901猫.exe"では失敗しないことから、Unicode(UTF-16)での文字数ではなくShift_JIS(CP932)でのバイト数によるものと思われる。 さらに、これはExerb単体の問題と思われるが、「表」「能」「十」等のいわゆるShift_JIS(CP932)のダメ文字を実行ファイル名に使用すると、また別のエラーが起動時に出る。 これらのことから、恐らくUTF-8でもなく、Shift_JIS(CP932)表現での問題点なのだと思われる。 !!実験結果のサイズ一覧 以下に、この記事で扱った実行ファイル・DLLのサイズの一覧を挙げておく。 なお、7-zipの標準圧縮レベルと、UPXでの"--lzma"に付加するレベルの対応は良く分からなかった。 "-5"に相当するのかと思っていたのだが、試した限りでは''"--lzma"指定時は"-3"と"-5"と"-7"は同じ''サイズになった。 バイナリを比較すると2バイトほど違いはあるのだが、使用されるアルゴリズムは一緒なのだろう。 "-1"と"-2"と"-3"は結果が異なるので、''"--lzma"では3段階しか選択できない''ということだろうか。実験自体は"-5 --lzma"で主にやっていたので、これをメインに掲載しているが、"-3 --lzma"でも同じだと思われる。 ,Exerbのコア,wxRubyのDLL,UPX圧縮,サイズ ,static,含む,無圧縮,"10,801,152" ,static,含む,-9,"2,984,960" ,static,含む,--best,"2,982,400" ,static,含む,-1 --lzma,"2,927,104" ,static,含む,-2 --lzma,"2,838,016" ,static,含む,-3 --lzma,"2,520,064" ,static,含む,-5 --lzma,"2,520,064" ,static,含む,-7 --lzma,"2,520,064" ,static,含む,7-zip標準(参考),"2,192,972" ,static,含む,7-zip超圧縮(参考),"2,125,313" ,static,含まない,無圧縮,"712,704" ,static,含まない,-1 --lzma,"272,896" ,static,含まない,-5 --lzma,"''257,024''" ,static,含まない,7-zip標準(参考),"249,249" ,DLL,含まない,無圧縮,"212,992" ,DLL,含まない,-1 --lzma,"56,832" ,DLL,含まない,-5 --lzma,"''53,248''" ,DLL,含まない,7-zip標準(参考),"44,408" ,DLL名,UPX圧縮,サイズ ,exerb50.dll,無圧縮,"598,016" ,exerb50.dll,-1 --lzma,"257,024" ,exerb50.dll,-3 --lzma,"243,712" ,exerb50.dll,-5 --lzma,"''243,712''" ,exerb50.dll,7-zip標準(参考),"235,214" ,wxruby2.so,無圧縮,"10,088,448" ,wxruby2.so,-1 --lzma,"2,174,976" ,wxruby2.so,-3 --lzma,"1,790,464" ,wxruby2.so,-5 --lzma,"''1,790,464''" ,wxruby2.so,7-zip標準(参考),"2,278,592" ,wxruby2.so,7-zip超圧縮(参考),"2,262,427" ,wxruby2.so,7-zip標準(BCJ)(参考),"1,948,980" ,wxruby2.so,7-zip超圧縮(BCJ)(参考),"1,886,011" 主な合計サイズの比較は以下の通り。 ,Exerbのコア,wxRubyのDLL,UPX圧縮,サイズ ,static,.exeに含む,無圧縮,"10,801,152" ,static,.exeに含む,-9,"2,984,960" ,static,.exeに含む,-1 --lzma,"2,927,104" ,static,.exeに含む,-5 --lzma,"2,520,064" ,static,.exeに含む,7-zip標準(参考),"2,192,972" ,static,.exeに含む,7-zip超圧縮(参考),"2,125,313" ,static,外部wxruby2.so,-1 --lzma,"2,447,872" ,static,外部wxruby2.so,-5 --lzma,"'''2,047,488'''" ,外部exerb50.dll,外部wxruby2.so,-1 --lzma,"2,488,832" ,外部exerb50.dll,外部wxruby2.so,-5 --lzma,"''2,087,424''" !サイズ一覧を読む際の注意 表をよく見ればわかるが、実行ファイルを圧縮する場合に、UPXが常に7-zipより小さくなるわけではない。UPXの本来の利点は「そのまま実行できる状態で小さくなる」点にあることをお忘れなく。 一応、7-zipの方が小さくなる理由として考えられるものは以下の通り。 *UPXでは自己展開コードが付く。 *UPXでは実行ファイルの体裁を保つため圧縮できない領域がある。 *UPXの前処理が効きにくい実行ファイルも存在し、相性がある。 *7-zipにも命令コード用の前処理(BCJ)は存在し、相性がある。 *LZMAの内部的オプションの使い方は、本家である7-zipの方が上手いはず。