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秒