【しばらく編集不可モードで運営します】 編集(管理者用) | 差分 | 新規作成 | 一覧 | RSS | FrontPage | 検索 | 更新履歴

Firefox3MemoryUsage -

目次

Firefox 3 のメモリ使用量

この文書について

Firefox 3 のメモリ使用量

Web や Web ブラウザが成熟するにつれ, 人々はそこになかったものを期待するようになった. はじめて Firefox がリリースされた頃, タブやアドオンを使う人はほとんどいなかった. Firefox を書いたのは Web の使われかたが変わる前だったから, メモリのようなシステムの資源を効率利用する戦略もそれに基づいていた.

Firefox 2 は先代の Firefox 1.5 より少ないメモリしか使わなかった. とはいえ, 私たちは意図的に Firefox の載る Gecko プラットホームへの変更を制限していた. (その Gecko 1.8.1 は, Gecko 1.8 と少し違うだけだ.) 多くの人々が Firefox2 / Gecko1.8.1 で作業をしている一方で, 私たちの中には Firefox 3 のプラットホームである Gecko 1.9 に突き進む者もいた.

そのプラットホームに, 私達は数え切れないほど大きな変更を加えた. 変更は多数のメモリ使用量削減が含んでいる. 結果は劇的なものとなった. 最近リリースされた Firefox 3 Beta 4 を試してもらえれば, 自ら効果を実感できると思う.

私たちのやったこと

メモリ断片化の削減

以前書いたように, ブラウザのように長時間稼動するアプリケーションでは メモリの断片化によって大きく空間効率を損ねうる. 様々なサイズのアロケーションをしたり, 空領域に再利用の難しい小さな隙間を大量に残したりして断片化が起こる.

改善に向けて私達の使った方法のひとつは, 合計のアロケーション回数を減らすことだった. 不要なメモリ確保を減らすのだ. コードベースのほぼ全ての場所で, このアロケーションの削減を行った. 以下のグラフは起動時のアロケーション回数を示している. グラフから伺えるように, 回数は 1/3 以上減った! Olli Pettay, Jonas Sicking, Johnny Stenback, Dan Witte がこれら全ての大変更をやってくれた.

私は様々なアロケータで断片化の効果をよく調べ, jemalloc が長時間稼動後の断片化を最小にするという結論に至った. 私は jemalloc 作者 Jason Evans と緊密に連携して作業をすすめ, jemalloc を我々のプラットホームに移植, チューニングした. Jason は気が遠くなる努力をして, jemalloc のコード行数も 2 ヶ月で倍に増えた. 苦労したとはいえ, 成果はその甲斐あるものになった. Beta 4 の時点では Windows と Linux 双方で jemalloc を使っている. Windows Vista 上のテストによれば, チューン版 jemalloc を有効にすると メモリ使用量は 22% 減少した.

循環コレクタによる循環の回避

ある種のリークは他のリークより根が深い. 最も困難なリークのひとつは, 二つのオブジェクトが相互に参照しあい, 生き延びてしまうものだ. これは循環という. そして循環はいただけない. これまでのバージョンでは, 私達は複雑で厄介なコードを使って循環を手動で切っていた. しかしこのコードを正しく保つのは大変な仕事だといつも身に染みていた. Gecko 1.9 で, 私達は自動の循環コレクタを実装した. これはメモリ上の循環を検知し, それを自動で切ってくれる. これはすばらしい仕組みだ. 私達のコードの複雑さを大いに軽減してくれた. 拡張機能にとって, この効果は顕著だった. 拡張機能は気付かず不用意な循環を持ちこみがちだった. それが Firefox 内部のすべてにアクセスできたからだ. 拡張機能の作者すべてに手動で循環を切れといっても現実的ではないだろう.

基本的に, 循環コレクタのおかげで, 本体のコードでも拡張機能でもリークは簡単に 避けられるようになった. これは皆にとって良いことだ. Graydon Hoare, Peter Van der Beken, David Baron による驚異のハードワークに感謝したい.

キャッシュのチューニング

Firefox は各種のインメモリキャッシュを使い, 性能向上を行っている. キャッシュには画像キャッシュ, 戻ると進むの移動を高速化する後方/前方キャッシュ, 文字描画を高速化するフォントキャッシュ, などがある. 私達はそれぞれのキャッシュがどれだけの大きさで, どれくらい長く保持されるのかを調査した. 多くの場合にキャッシュの期限切れポリシーを設けることで, 大事な場面の性能改善に寄与しつつメモリを食い尽さないようになった.

現在では前方/後方キャッシュの文書を 30 分後に無効化している. どうせそのページにすぐ戻ることはないだろうから. タイマーを使ったフォントキャッシュ, フォントメトリクスの計算結果キャッシュも導入した. これらの寿命は短いものだ.

以下に書いたように非圧縮画像データも破棄するようになった...

画像データの保存方法調整

Firefox3 におけるもう一つの大変更は, 画像データ周辺の改善だ.

Web の画像は圧縮形式(GIF, JPEG, PNG など)でやってくる. 画像をロードすると, メモリに非圧縮の状態で展開される. Firefox 2 では, 数時間も裏側のタブにある画像まで非圧縮のまま保持していた. Firefox 3 では, Federico Mena-Quintero (GNOME で有名) の活躍によって 一定時間使われなかった非圧縮データを破棄するようになった. 背後のタブにあるページの画像だけではなく, メモリキャッシュにはあるものの文書にアタッチされていない画像にも影響する. この効果はアクティブに見てないページの画像に対し, すばらしく劇的なメモリ削減を行った. 100KB の JPEG は展開すると数メガバイトになるが, 表示しないあいだは, この非圧縮メモリのサイズが課されることはない.

Alfred Kayser によるまた別の鮮やかな変更は, メモリ消費量を減らす アニメーション GIF の保存方法に関するものだ. 今ではアニメーション GIF を ピクセル毎 8 ビットデータのパレット形式で保存し, 32 ビットは使わない. これは長いアニメーションでのメモリ使用を大きく節約できる. あるバグで示された極端な例のひとつでは, 368MB が 108 MB になった. 260 MB の節約!

リーク狩り

複雑なソフトウェアなら何であれ, 大半のメモリリークは探したり修正したりが難しく, 苦痛の伴うものだ. 小さなリークがあり, 大きなリークがあり, 中くらいのリークがあった. 一時間にひとつ小さなテキスト片がひとつリークするくらいでは, 使っていても気がつかない. カーソルを動かすたびに巨大な画像がリークしたら大問題だ. どちらも修正するのが大切だ. 塵も積もれば山となる. ページを閉じるまでの間だけのリークがある. このリークは一般的なリーク検出ツールでは 見つからない. しかし GMail のように一日中開いておくページだと差が出てしまう.

Leak Hunt (ファミコンの『ダックハント』の画面 TBD: image)

リーク狩りに関して Ben Turner の腕は一流だ.

私達は多くのリークを修正した. GMail のサイトを閉じる時にリークする 小さな DOM オブジェクトから, ウィンドウを閉じたときにまるごと中身を抱えこんでしまう リークまで.

全部あわせると, 私たちは 400 近くのリークを修正した. 大半はとても稀なもので, しかし起きうるものだった. 私達はリーク検出ツールをきわめて強力なものにした. 特に Carsten Book は リークの発見と報告に目覚ましい活躍をみせてくれた.

メモリ使用量の測定

メモリ使用量を正確に測るのは難しい. 私は痛い目に遭って学んだ.

この節の内容は少し技術的だ. 読むのを飛ばして構わない. 短くまとめるとこうなる. Windows Vista (コミットサイズ) と Linux (RSS) は 至って正確なメモリ計測の数値を出す. 一方の Windows XP と MacOS X は正確でない.

読者が Windows Vista を使っているなら, タスクマネージャのコミットサイズを 見ているといい. その数字はとても正確なものだ. XP でメモリ使用量をみると, その数字はあまり良いものじゃない. 理由はこうだ: Microsoft は "private bytes" の意味を XP から Vista で(良い方に) 変更した.

XP ではこの数字がアプリケーションの予約(reserve)した仮想記憶サイズになる. 性能上の理由で実際に使うより多くのメモリを予約することはよくある. アプリケーションはオペレーティングシステムに, 予約した領域の一部は使う予定はなく, その仮想空間に対しては物理空間を割り当てないように伝えることができる(訳注1). Vista では, private bytes はコミットサイズをあらわす. これは アプリケーションが実際によく使うメモリの量だけを数えたものだ. 仮想記憶のサイズはコミットサイズより大きいか同じくらいだから, XP のメモリ使用量は Vista より多く見える. 実際には同じ量しか使わなくてもね.

Mac では, Activity Monitor が実際に使っている以上のメモリを使っているような表示をする. Mac OS X は Windows XP と似たような, しかし異なる問題を抱えている. 徹底的なテストをしたあと Apple の従業員に確認したところ, アロケータがアドレスの範囲を予約したまま未使用のページをシステムに戻す方法はないことがわかった. (unmap して remap することもできるが, これには競合条件があり, うまくない.) こうした機能を持つと主張する API はある. (madvise() と msync()), しかし これらは実際のところ何もしない. マップされたが書き込まれていないページはメモリの統計対象にならないようだ. しかし書き込むと, unmap するまで場所をとるようになる. 普通は大量の未書き込みページをマップしておくことはない. アプリケーションは空きページを再利用できるし, 実際するだろう. だから Firefox がメモリ使用量の最大値を記録したあと, それより多くのメモリをつかわないという状態を目にすることだろう.

Linux のメモリ使用量の報告はいたって良好だ. Linux は madvise() をサポートする. おかげで Linux では不要なページを システムに教えることができる. だから常駐メモリサイズ(resident set size)は とても正確だ. RSS を測るには ps や top を使うことができる.

テスト方法

ブラウザのメモリ使用量を測る方法は色々ある. 10 個のタブにお気にいりのサイトを開いて, ブラウザのメモリ使用量を見てみよう. 最後のタブ以外すべてのタブを閉じ, 最後のタブには about:blank や Google をロードする. そしてもう一度図ってみよう. 単純なテストをもうひとつあげるなら, ただ Zimbra や Google Reader や Zoho をそれぞれのタブにロードし, ログインしてみよう. ユーザはあまりに様々なことにブラウザを使うため, 単一のテストを作ってメモリ使用量を測るのはほとんど無理だ. 私達はそう学んだ.

負荷テストにはより多くを望んでいる - ウェブからランダムなサイトをロードするよりは再現性の高い方法が欲しい. 私達は Standalone Talos フレーワークを利用した. また Mike Schroepfer はそれに 変更を加え, ウィンドウ一揃いの中でページを循環するようにした. ウィンドウは閉じては開かれ, 人間の長時間の動作を近似しようとした. Talos はこのようにとても素直な動作をする. これがメモリ使用量やレイアウト速度の計測にはとても有用だった. この仕組みは Firefox の性能その他の指標をとるのにとても役だったが, ページ循環の仕組みは他のブラウザだとうまく動かなかった.

私達はブラウザをまたいだテストをしたかったので, クロスブラウザで動くようテストを変更した. そして talos のコードと Windows のパフォーマンスカウンタを繋ぎ, private bytes (Vista でのコミットサイズ)を計測した.

以下に示す結果では, 29 の異なるサイトを 30 のウィンドウにロードする動作を 11 周した. (計 319 ページロード.) 各ページのロードでは常に新しいウィンドウを開いた. (ウィンドウが 30 枚開いたら, 一番古いウィンドウを閉じた.) 最後には一枚を除きすべてのウィンドウを閉じ, ブラウザを数分放置した. メモリを回収し, 短期キャッシュをクリアするなどの動作を見るためだ. ページロードの間隔は 3 秒の遅延を挟んだ. 全てのブラウザで同じだけ時間をとるためだ. Standalone Talos 組込みの Proxy サーバを使い, 全てに同じコンテンツがロードされるようにした. テストウィンドウが 30 のウィンドウを開いてテストできるよう, popup block は無効にした. ここに簡単なテスト用ウェブページがある. メモリ使用量を観測する python スクリプトはここ. これらは standalone talos フレームワーク上に構築されており, 結果を得るには python スクリプトを talos フレームワークに食わせる必要がある. 熱意が Mike Schroepfer を支え, これを実現した.

結果

グラフを読んでみよう.

このテストは成し遂げたすばらしい成果を示す一例にすぎない. テストの追加を続け, ユーザがブラウザを使う方法をより多く計測していきたい.

まとめ

私達の作業は実りあるものだった.

前バージョンの Firefox や他のブラウザよりずっと小さくなった.

より長時間, より少ないメモリでブラウザを開きっぱなしにできる.

拡張機能もずっとリークしにくくなった.

新しいコードでのリークを検出する自動化ツールを作った. 常に監視とテストを続け, 正しい方向に進んでいることを確かめている.

これら全てによって, 劇的な性能改善を達成することができた.

謝辞

多くの人々がこの仕事をしたが, 特に以下の人々には感謝したい: David Baron, Carsten Book, Peter Van der Beken, Igor Bukanov, Brendan Eich, Jason Evans, Alfred Kayser, Federico Mena-Quintero, Robert O’Callahan, Olli Pettay, Mike Schroepfer, Mike Shaver, Jonas Sicking, Johnny Stenback, Ben Turner, Vladimir Vukicevic, Dan Witte, Boris Zbarsky, そして私がここで忘れているけれど, 作業をしてくれた人々にも. みなが力をあわせ, なしとげることができた.


(訳注1) 原文は“The application can tell ... to not back the virtual space with physical space.” 当初は「仮想空間を物理空間に戻さない (訳注:よくわからず) 」とされていました。 ここでは予約(=仮想空間のみ予約)とコミット(=物理的な記憶領域を予約)との違いについて述べています。よって「物理空間を仮想空間の後ろ盾としない(=仮想空間に物理的な記憶領域を割り当てない)」と考えれば自然だと思われます。