トップ 検索 一覧 差分 ソース ヘルプ RSS ログイン

wxRuby + Exerb + UPX 2009

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になる。…ちょっと待てよ。

分離圧縮のススメ

筆者は元記事の「おまけ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の方が上手いはず。

最終更新時間:2009年12月06日 06時14分19秒