トップ 検索 一覧 ヘルプ 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''になる。'''…ちょっと待て'''よ。

!分離圧縮のススメ

筆者は[[元記事|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の前処理が効いた結果''と言ってよいだろう。
実際に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版で使用しないのは、2つの理由からだ。
ひとつは、UPXの展開コードが.exeや.dll/.soのファイルごとについてしまうため、''重複してしまい冗長''であること。
もうひとつの理由は、DLLに分離した場合のDLL読み込みコードが省略され、元のデータ自体が小さいもので済むためだ。
実行ファイルが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にも命令コード用の処理は存在する。
*7-zipにも命令コード用の前処理(BCJ)は存在し、相性がある。
*LZMAの内部的オプションの使い方は、本家である7-zipの方が上手いはず。