Visual Studio “15” でのメモリ不足によるエラーが減少

本記事は、マイクロソフト本社の The Visual Studio Blog の記事を抄訳したものです。【元記事】 Reduced Out of Memory Crashes in Visual Studio “15” 2016/10/12

 

今回の記事は、Visual Studio “15” Preview 5 のパフォーマンス強化についてお伝えする 5 回のシリーズの第 3 回です。これまでの 2 つの記事では、Visual Studio “15″ で可能になった起動時間の短縮ソリューションの読み込み時間の短縮についてお伝えしました。

Visual Studio には多様な機能が詰め込まれており、実に多くの開発者が業務で活用しています。高い応答性を確保しながらこれらの機能をサポートするためには、大量のメモリが必要になります。しかし、Visual Studio 2015 では特定のシナリオでメモリ使用量が増えすぎることで、メモリ不足によりクラッシュしたりインターフェイスの応答が悪くなったりするという報告がお客様から多数寄せられました。VS “15” では、Visual Studio の高い機能性とパフォーマンスを維持しながら、これらの問題を解決することに取り組んでいます。

Visual Studio ではさまざまな分野で機能の最適化を進めていますが、今回の記事では JavaScript と TypeScript の言語サービス、デバッガーでのシンボルの読み込み、VS での Git のサポートの 3 つの分野について説明します。この記事では、全体を通じてそれぞれのシナリオで測定した下記の 2 つのメトリックを比較し、改善状況についてお伝えします。

ピーク時の仮想メモリ使用量: Visual Studio は 32 ビット アプリケーションであるため、仮想メモリの使用量は 4GB に制限されます。仮想メモリに割り当てられたメモリがこの制限を超過すると、「メモリ不足 (OOM: Out of memory)」エラーが発生し Visual Studio がクラッシュします。ピーク時の仮想メモリ使用量は、この 4GB の制限にプロセスがどの程度近付いたか、つまりプロセスがクラッシュするまでどの程度の余裕が残っていたかを示します。

ピーク時のプライベート ワーキング セットのサイズ: 物理メモリ内には、プロセスが実行するコードや使用するデータが含まれる仮想メモリのサブセットが必ず存在します。「ワーキング セット」とは、その物理メモリの使用量を表すメトリックです。このワーキング セットの一部である「プライベート ワーキング セット」は、特定のプロセスのみが使用しているメモリです。これは複数プロセスの間で共有されないため、メモリ使用量が比較的大きくなります。この記事では、Visual Studio (devenv.exe) プロセスとそれに関連するサテライト プロセスのプライベート ワーキング セットのピーク時の使用量の計測値を示します。

JavaScript 言語サービス

Visual Studio 開発者の 3 人に 1 人は日常的に JavaScript (JS) コードを作成しており、JS 言語サービスを多数の Visual Studio セッションのコンポーネントの 1 つとして読み込んでいます。JS 言語サービスは、JS の編集エクスペリエンスの生産性を高める IntelliSense やコード参照などの機能を提供します。

このような生産性機能をサポートしつつ高い応答性も確保するために、言語サービスはかなりのメモリを消費します。メモリ使用量はソリューションの内容によって変化し、主にプロジェクト数、ファイル数、ファイル サイズの影響を受けます。さらに、JS 言語サービスは C# などの他の言語サービスと同時に VS に読み込まれることも多いため、プロセスでのメモリ使用量がさらに増加する原因となります。このため、メモリ不足による VS のクラッシュを減少させるためには、JS 言語サービスのメモリ使用量の削減が不可欠です。

VS “15” では、JS コードのサイズや内容によらず、メモリ使用量の増加が Visual Studio の信頼性に影響しないよう対策が実施されています。JavaScript の編集エクスペリエンスの品質を低下させることなくこの目標を達成するために、VS “15” Preview 5 では JS 言語サービス全体をサテライト プロセスの Node.js プロセスに移行し、そこから Visual Studio に処理内容を返すようにしました。また、JavaScript と TypeScript の言語サービスを統合し、両方の言語サービスが読み込まれるときのセッションの総メモリ使用量を削減しました。

メモリへの影響を測定するために、下記のシナリオの場合の Visual Studio 2015 Update 3 と VS “15” Preview 5 の結果を比較しました。

  • WebSpaDurandal (英語) ソリューションを開きます。このソリューションは Asp.Net のサンプルで、VS で開かれた JS コードのサイズの 95% パーセンタイル値を表しています。
  • _references.js を作成し自動同期を有効にします。
  • 10 個の JS ファイルを開きます。
  • 編集、補完機能の使用、ファイルの作成や削除、フォーマット ツールの実行を行います。

このシナリオでは下図のような結果が得られました。

グラフ 1: JavaScript 言語サービスのメモリ使用量

Visual Studio のピーク時の仮想メモリ使用量は 33% 削減され、JS 開発者にとってメモリ不足によるクラッシュを減少させるには十分な効果が得られます。Preview 5 ではピーク時のプライベート ワーキング セットの合計サイズが Visual Studio プロセスとその子ノード プロセスの合計を表していますが、こちらは Visual Studio 2015 とそれほど変わりません。

デバッガーでのシンボルの読み込み

デバッグ作業の生産性を高めるためには、シンボル情報が不可欠です。マイクロソフトの最新の Windows 用コンパイラでは、シンボル情報は PDB ファイルに格納されています。PDB ファイルには、関数名、実行形式のバイナリのオフセット、実行ファイルで定義されているクラスや構造の型情報、ソース ファイル名など、該当するコードに関する情報が大量に含まれています。Visual Studio のデバッガーが呼び出しスタックを表示したり変数や式を評価する場合、それに対応する PDB を読み込み、その中の関連する部分を参照します。

Visual Studio 2012 よりも前のバージョンでは、複雑な Natvis ビューで型の評価を行っていましたが、パフォーマンスはあまりよくありませんでした。これは、大量の型情報をオンデマンドで PDB から取得するときに、ディスク上に存在する PDB ファイルへのランダム IO が多数発生するためです。従来型のハード ディスク ドライブでは、よいパフォーマンスを得ることは困難です。

Visual Studio 2012 では、この機能が C++ のデバッグ エクスペリエンスに追加され、デバッグ セッションの初期に大量のシンボル データを PDB から事前に取得するようになりました。これにより、型を評価するときのランダム IO がなくなり、パフォーマンスは大幅に向上しました。

しかしこの最適化の結果、場合によっては必要以上の量のシンボル データが大量に読み込まれ、事前に取得されるシンボル データの量が多くなりすぎるという問題が発生しました。たとえば、呼び出しスタックが表示されるときに、[Locals] ウィンドウや [Watch] ウィンドウなどの型の評価が不要なデータを含め、スタックのすべてのモジュールからシンボル データが事前に取得されていました。大規模なプロジェクトにはシンボル データを使用可能なモジュールが多数含まれており、各デバッグ セッションでのメモリ使用量はかなり大きくなります。

VS “15” Preview 5 では、事前取得により向上したパフォーマンスを維持しながら、シンボル情報が使用するメモリの量を削減しました。今回のバージョンでは、変数や式の評価や表示に必要なモジュールのみが事前取得されるようになりました。

これについて、下記のシナリオでメモリ使用量の変化を測定しました。

  • Unreal Engine ソリューションの UE4.sln を読み込みます。
  • Unreal Engine エディターを起動します。
  • VS デバッガーを Unreal Engine のプロセスにアタッチします。
  • E:\UEngine\Engine\Source\Runtime\Core\Public\Delegates\DelegateInstancesImpl_Variadics.inl の 640 行目にブレークポイントを設定します。
  • ブレークポイントまで実行します。

このシナリオでは下図のような結果が得られました。

グラフ 2: VS デバッガーを Unreal Engine のプロセスにアタッチした場合のメモリ使用量

このシナリオでは、VS 2015 はメモリ不足によりクラッシュしました。VS “15” Preview 5 の仮想メモリ使用量は 3GB、プライベート ワーキング セットのサイズは 1.8GB でした。明らかに改善されてはいますが、満足な結果とは言えません。マイクロソフトでは、ネイティブなデバッグ作業シナリオでのメモリ使用量の削減を、今後の VS “15” 開発でさらに進める予定です。

Visual Studio での Git のサポート

Visual Studio に Git のサポートが追加されたときには、libgit2 というライブラリが使用されていました。libgit2 では、さまざまな操作時に Git のインデックス ファイル全体をメモリにマッピングします。リポジトリのサイズはインデックス ファイルのサイズに比例します。このため、大きなリポジトリを使用する場合、Git の操作により仮想メモリで大きなスパイクが発生します。既に仮想メモリ容量が逼迫している場合、このスパイクにより VS がクラッシュする場合があります。

VS “15” Preview 5 では libgit2 を使用せずに git.exe を呼び出すようにして、仮想メモリのスパイクが VS のプロセス外で発生するようにしました。git.exe の使用により、VS のメモリ使用量が削減されただけでなく、機能性も向上し開発が容易になりました。

Git 操作でのメモリ使用量の変化を測定するために、下記のシナリオで Visual Studio 2015 Update 3 と VS “15” Preview 5 の結果を比較しました。

  • チーム エクスプローラーで Chromium リポジトリ (英語) を開きます。
  • [Changes] パネルに移動して保留中の変更を表示します。
  • F5 キーを押して画面を更新します。

このシナリオでは下図のような結果が得られました。

グラフ 3: チーム エクスプローラーの [Changes] パネルを更新した場合のメモリ使用量の変化

VS 2015 では、更新中に 300 MB 程度の仮想メモリのスパイクが発生しました。VS “15” では、仮想メモリの使用量増加は見られませんでした。プライベート ワーキング セットのサイズの増加は VS 2015 では 79 MB、VS “15” では 72 MB で、これはすべて git.exe によるものです。

まとめ

VS “15” では、Visual Studio のメモリ使用量の削減に尽力しています。この記事では、3 つの分野での進展についてお伝えしました。まだ改善すべき課題は残っており、今後もさらに努力していきます。

マイクロソフトでは以下のさまざまな方法で皆様のご協力をお願いしています。

  • マイクロソフトでは、プレリリース版を含めたすべてのリリースでテレメトリをモニタリングしています。ぜひ VS “15” Preview 5 をダウンロードしてご利用ください。日常的に使用される外部ソースのデータが増加することで、マイクロソフトが参考にさせていただくシグナルの質も向上します。
  • メモリの高負荷 (またはその他の品質) 問題が発生した場合は [Report a problem] ツール (英語) からご報告ください。こちらの環境で問題を再現できるような形でご報告いただけると大変助かります。問題を再現できるサンプルや実際のソリューションをお送りいただけますと幸いです。このような対応が難しい場合は、問題発生時の記録を添付し ([Report a problem] ツールより簡単に収集できます)、事象についてできるだけ詳細な内容をご報告ください。
Ashok Kamath (Visual Studio 担当主任ソフトウェア エンジニアリング マネージャー)Ashok Kamath は Visual Studio のパフォーマンスおよび信頼性を担当するチームのリーダーで、以前は .NET 共通言語ランタイム チームに所属していました。