読者です 読者をやめる 読者になる 読者になる

UPX 圧縮するとメモリ使用量が増える事例

.NET

UPX 圧縮や .NETJavaJIT メカニズムは,実行イメージを実行時に展開するという共通点を持っています.これは必ずしもメリットばかりではありません.特にメモリについては,次のような影響が現れます.

  1. 同じ実行ファイルから複数のプロセスを生成するとき,ファイルイメージの共有によるメモリ使用量の削減効果が得にくい
  2. 実行時生成された実行イメージは,元のファイルとは異なるため,ページファイルに待避し直す必要がある

順に見ていきましょう.



exe ファイルや dll ファイルは,メモリマップによってプロセス空間にマップされます.このとき,読み取り専用なセグメントや,まだ一度も書き換えられていないページは,実メモリ上に1つだけ実体を起き,アドレス変換によってプロセス間で共有することができます.
Process Explorer を使用すると,プロセス中の DLL がどれぐらいワーキングセットを消費しているかを知ることができます.

このプロセスのワーキングセット約 22 MB のうち,約 9% にあたる 2 MB の領域は,ATOK19W.IME のマッピングに消費されていることが分かります.しかし,そのほとんどは共有可能領域であり,876 KB の領域は他のプロセスと共有されていることも分かりました.このプロセスと全く同じプロセスが 10 個あったとしても,ATOK19W.IME による実メモリ使用量は 2 MB × 10 にはならないでしょう.
プロセスごとのワーキングセットを単純加算すると重複カウントが発生します.プロセスごとのワーキングセットを単純に比較するという方法には「共有度」という視点が足りません.このプロセス全体では 22 MB のワーキングセットを持っていますが,そのうち 9.7 MB は共有可能で,7.2 MB は実際に他プロセスと共有されています.タスクマネージャは事実を示しますが,見る人は真実ではなく見たい結論を見ようとします.
ディスクキャッシュというと,複雑で深遠な仕組みを想像するかもしれませんが,まさにこの 7.2 MB はキャッシュとして働きます.exe ファイルや dll をメモリマップしたときに,既に他の誰かによって実メモリにマップされていた領域は,ほとんどノーコストですぐに利用可能です.
FirefoxInternet Explorer の比較で Internet Explorer に有利な点があるとすれば, Internet Explorer が利用するライブラリは Microsoft によって作られた他のプロセスでも使用されていることが多いという点でしょう.「OS と融合している」とかそういう怪しげな表現を使うまでもなく,流行っているものはより効率が良くなるというルールが元々存在するわけです.
ライブラリを DLL 化し,そして多くのプロセスで共用するということには,コンポーネント化やディスク使用量の削減という意味だけでなく,起動時間の削減やメモリの効率的利用という効果も確かに存在するのです.


ここまで説明すれば,UPX 圧縮や .NETJavaJIT メカニズムが,メモリ使用量の観点からは悪影響を働きうることもご理解いただけるでしょう.実行時に展開するということは,ヒープ上に展開しなければならないということを意味します.ヒープのように対応するマップドファイルが存在しない領域を支えるのはページファイルの役目です.これが,ページファイルが消費されるということの意味です.さらに悪いことに,プロセスごとに独立して展開が行われると,プロセス間で実メモリの共有が行われません.これを実際に UPX 圧縮を行ったサンプルプログラムで見てみましょう.圧縮は UPX 2.0 を使用し,最高圧縮率を選択しました.

このサンプルプログラムを 10 個起動しても,ページファイル使用量 (コミットチャージ) はせいぜい 10 MB しか増えません.しかし,これを UPX 圧縮してから 10 個起動すると,ページファイル使用量は 160 MB 以上増加します*1


もちろん,Windows で同一プロセスを複数個起動するということは,実際にはそれほど行われるものではないでしょう.しかし,ファイルサイズが小さい方がメモリ使用量も「少なそうな気がする」といったイメージ戦略と戦うときに,こういった裏側の知識が役立つことがあるのもまた事実と言えます.

*1:この数字にはそれ程意味がありません.オリジナルサイズが 16 MB というかなり大きな exe ファイルを 10 個起動した,という程度の話です