RAD Studio XE7での「メモリ不足エラー」について

Abstract: RAD Studio XE7のIDEで発生する「メモリ不足エラー」に推奨される対処法

エンバカデロでは、RAD Studio XE7における「メモリ不足」問題に関連する問題報告をいくつか頂いています。ユーザーが見る実際のエラーメッセージは異なるかもしれませんが(アクセス違反を含む)、利用可能なメモリ領域が不足しているためにオブジェクトの生成に失敗することにより引き起こされるエラーです。他の最近のバージョンでも似たような問題が発生することがありましたが、何人かの開発者においてはXE7でより顕著に発生しています(他の開発者は以前のバージョンよりもXE7のほうが安定していると報告していますが)。

エンバカデロが頂いているフィードバックの例(および、いくつかの適用可能な解決策)は、以下のQuality Portal報告のコメントでご覧いただけます。

https://quality.embarcadero.com/browse/RSP-9568

エンバカデロのR&Dチームはいくつかのメモリ不足のシナリオに対処するための解決策に積極的に取り組んでいますが、実はXE7には、既に似たような状況に対処するための複数のオプション(「コンパイラを別プロセスで実行する」の利用など)が含まれています。最近受けた報告ののち、R&Dでは恒久的で長期的な解決策に取り組む一方、採用可能なあらゆる回避策を研究しつつ、この方向性により労力をかけるようになりました。

しかしこの文書は、それらのより長期的な計画を強調するというよりも、私たちの目標はユーザーが現時点で実装を検討すべきいくつかの解決策、回避策、および提案を提供することです。ユーザーの中には、問題を克服するために今回の提案や回避策を使用している方もいます。これらの問題は、主に大規模なアプリケーション(または多くのアプリケーションを含む大規模なプロジェクトグループ)で発生しますが、コード、プロジェクト管理、およびアーキテクチャによって非常に異なります。

    1. なぜIDEはそれほど多くのメモリを必要とするのか?

最初の疑問は、なぜRAD StudioのIDEはそれほど多くのメモリ(過去のバージョンよりも多い)を必要とするのか? です。いくつかの主な理由は、コードを記述する際の生産性向上を支援するコードインサイトのサポート(コード補完やエラーインサイトなど)のために使用されている構文解析技術に関係しています。2つ目の大きなメモリ消費は、コンパイル速度を飛躍的に向上させるためにコンパイル済みDCUをキャッシュしているコンパイラ自身に起因します。デフォルトでは、コンパイラはIDEと同じメモリ空間を共有し、デバッガもまた同じ情報を利用しています。

次の疑問は、なぜXE7ではIDEによって使用されるメモリが増加したのか? です。基本的には、IDEがより多くの機能や複数のコンパイラ、RTL、コンポーネントライブラリ、およびその他の多くのサブシステムを搭載するようになったことに帰着します。個々の機能は少しだけ追加のメモリ要件を必要とするかもしれませんが、すべてが一緒になるとメモリが増加することになります。

この種のメモリ問題は「天井問題」と言われます。つまり、IDEで作業をし、アプリケーションをコンパイルする際のIDEのメモリ使用量が利用可能なメモリの95%であった場合、問題はありません。別の2%または3%のメモリを消費すると、天井に達する可能性があります。また別の数%のメモリを消費すると、メモリ不足エラーが発生することになるでしょう。XE7を使用して以前のバージョンのアプリケーションプロジェクトに対して作業を行うと問題が発生することがあるのはそのためです。その間、エンバカデロのR&Dチームが以前のバージョンのIDEに存在していた不具合およびリークを修正したとしても、新機能に必要な追加のメモリ量や、ユーザーのプログラムにさらにユニットを追加した際のメモリ量比べれば、メモリの節約量は少量です。

詳細な技術用語ではもう少し複雑なものになりますが、この説明によって今回の問題がXE7で新たに発生するようになったエラーによるものではない、ということを理解するのに役立つと願っています。エンバカデロでは似たようなシナリオを探し出すのにかなり時間を費やしましたが、現状のエラーの根本原因になりそうな製品のあらゆる領域において目立ったメモリリークは見つかりませんでした。このように、これらのシナリオをメモリリーク(ユーザーは目にしたことがあるかもしれませんが)ではなく、「メモリ不足(またはOOM)エラー」と記述するのが正しいのです。

もう1つ考慮すべき要素があります。いくつかのケースでは、問題は単にIDEが使用できるメモリ合計量ではなく、ある時点で利用可能な連続するメモリの合計量です。言い換えると、まだ十分なメモリが利用可能であるような場合であってもメモリ不足エラーが発生するかもしれませんが、メモリは非常に断片化されています(多くの割り当ておよび解放の繰り返しの後に起きることがある)。現在のプロジェクトグループを閉じると(およびIDEも再起動すると)、大幅に改善されることがあるのはそのためです。

    2. 別プロセスでビルドする

もしメモリ不足問題(またはそれに関連する問題)が発生した場合に最初にすべきことは、IDEとは別のプロセスでコンパイルする(または、より正確には、別プロセスでMS Buildを実行する)オプションを有効にすることです。つまり、IDEのメモリ空間内でコンパイラをDLLとしてロードするのではなく(構文解析などのために使用されるメモリが追加されます)、別プロセスを生成しそこでコンパイラを実行し、コンパイルが終わるとプロセスを終了させて使用していたすべてのメモリを解放することにより、IDEがユーザーからのコンパイル要求を処理します(単にシンプルなCtrl+F9キー押し下げを含む)。これには2つの効果があり、1つはコンパイラがより多くのメモリを使用できるようなること、もう1つは各操作の後に完全な後始末を行うことです。欠点は、DCUのキャッシュを持たないために一般的にコンパイルに時間がかかることであり、アプリケーションをデバッグする必要がある場合には、実行ファイルにデバッグ情報を附加する必要があります。

手順:

  • メニューから[プロジェクト]-[オプション]-[Delphi コンパイラ]を選択し、'外部で MSBuild を使用してコンパイル'を true に設定する(プロジェクトグループの場合は、それぞれのプロジェクトごとに行う)
  • メニューから[プロジェクト]-[オプション]-[Delphi コンパイラ]-[コンパイル]-[デバッグ]を選択し、'デバッグ版 DCU の使用' を false に設定する
  • メニューから[プロジェクト]-[オプション]-[Delphi コンパイラ]-[リンク]を選択し、'リモート デバッグ シンボルを含める' を true に設定する

この手順はXE7のdocwikiでも説明されています:

http://docwiki.embarcadero.com/RADStudio/XE7/ja/F2046_メモリ不足_(Delphi)

C++Builderでは、アプローチは同じですが、手順は少し異なります:

  • メニューから[プロジェクト]-[オプション]-[プロジェクト プロパティ]を選択し、'C++コンパイラを別プロセスで実行する'を有効にする
  • その他のヒント:
    • プロジェクトのPATHに非ANSI文字が含まれる場合に、別プロセスでMS Buildを実行する際の問題が報告されています(https://quality.embarcadero.com/browse/RSP-9613)。
    • リンカのレベルでデバッグ情報を有効にしてください。そうでない場合デバッガは動作しません。
    • Delphiのアプリケーションで別プロセスでビルドする際にも少量のリークが存在しますが、それほど深刻なものではなく、問題が発生するまでには非常に長い時間がかかります(以下で説明しますが、ローカルでリモートデバッグを利用すればこのリークは解決します)。

    3. プロジェクトグループのサイズを削減する

エンバカデロでのテストによると、プロジェクトグループ内の非常に大規模なプロジェクトまたは多くのプロジェクトをコンパイル・ビルドするときに使用される大量のメモリは、実際にはリークではなく、プロジェクトグループを閉じるとそれらは回収されます。この操作はDCUキャッシュを解放します。実際にはすべてのプロジェクトを1つのグループ内でビルドする必要がない場合には、グループを分割することができるでしょう。分割すれば、一連のプロジェクトをビルドして、そのグループを閉じ、別のより小さなプロジェクトグループに対する作業に移ることができることに留意することが重要です。すべてを含む包括的なプロジェクトグループを作成すると確かに便利ですが、結果としてメモリ不足の問題の起こる可能性が高くなることがあります。

もしプロジェクトグループ内に多くのライブラリ(およびソースコード)を共有する複数のプロジェクトがある場合、それらのユニットはプロジェクトごとに再コンパイルされ、個別にキャッシュされることに特に注意してください。なぜなら、プロジェクトのコンパイラ設定が異なり、生成されたDCUに互換性がない可能性があるからです。ここでも、複数のプロジェクトを別々のグループに分離するのが役立ちます。しかしこれは、後述するように、実行時パッケージを活用するのに良いシナリオです。

    4. デバッガのフットプリントを削減する(パッケージを使用して)

スタンドアロンの実行可能ファイルをデバッグする場合、デバッガはそのシンボルをロードする必要があります。リンカは未使用の型を除去するので、ロードされている情報はデバッガが必要とするものです。

しかし、実行時パッケージを使用している場合はシナリオが異なります。実は、あなたがパッケージの中のたった1つのコンポーネントを使用していても、パッケージ全体を参照しています。これはデバッガに大きな負担をかけます。しかし、ほとんど知られていない機能ですが、デバッガにシンボルをロードさせたい対象の実行時パッケージを選択することがでます。

メインの実行可能ファイルをIDEでロードしてアクティブにする場合(プロジェクトグループの一部である場合)、以下のことを行えます:

  • メニューから[プロジェクト]-[オプション]-[デバッガ]-[シンボル テーブル]を選択し、'すべてのシンボルを読み込む'のチェックボックスのチェックを外す
  • 必要なモジュールのシンボルをロードするには、別の2つの方法があります:
  • デバッグを開始し、[モジュール]ビューでパッケージを右クリックして、最も有用なものに対するシンボルを選択してロードします
  • メニューから[プロジェクト]-[オプション]-[デバッガ]-[シンボル テーブル]を選択して、[新規作成]ボタンをクリックします。次に、[モジュール名]に実行可能ファイル名を、[シンボル テーブルパス]に $(OUTPUTPATH) を入力します。同じ作業を、デバッグに必要なパッケージに対してそれぞれ行います

デバッグに必要な変更があった場合には、プロジェクトオプション内のリストにシンボルテーブルを追加したり、リストから削除できます。

実行時パッケージを使用していない場合には、必ず以下メニューから実行時パッケージを有効にしてください。

  • メニューの[プロジェクト]-[オプション]-[パッケージ]-[実行時パッケージ]

その他のデバッグ方法として、ローカルのWin32アプリケーションに対しても、リモートデバッガを使用する方法があります:

  1. プラットフォーム アシスタント サーバー(PAServer)をインストールし起動する
  2. IDEでWin32向けのプロファイルを作成する
    1. メニューから[ツール]-[オプション]-[接続プロファイル マネージャ]-[追加]を選択する
    2. [プロファイル名]に名前を入力する
    3. [プラットフォーム]で '32 ビット Windows' を選択する
    4. 使用しているパソコンの実際のIPアドレス(またはマシン名)(localhostではないもの)を入力する
    5. [接続テスト]を使用して、PAServerに接続できるか確認する
  3. 「プロジェクトマネージャ」で[ターゲット プラットフォーム]-[32 ビット Windows]を右クリックして、[プロパティ]-[プロファイル]を選択し、手順2で作成したプロファイルを選ぶ
  4. これで、アプリケーションをデバッグする際に、アプリケーションは別プロセスのデバッガの下で動作し、IDEでよりたくさんのメモリを利用できるはずです

    5. 機能を少なくしたIDEイメージを作成する

アプリケーションのコンパイルおよびデバッグに必要なメモリ要件を削減する手法以外にも、いくらかのユーザーで機能しているその他の選択肢として、IDEの基本的なメモリフットプリントを削減することがあります。IDEはライブラリに対して動的な遅延ロードを使用していますが(例えば、VCLコンポーネントはVCLデザイナがロードされたときに初めて実際にロードされます)、結局ほとんどの場合は、特定のプロジェクトのために必要なものよりも、より多くの機能およびコンポーネントをメモリにロードすることになります。もちろん、これにはサードパーティのコンポーネントも含まれます。

主に使用している構成を変更するのではなく、コマンドラインパラメータ -r にシンボルを指定して、RAD StudioのIDEを実行するショートカットを作成することができます。「lean」という名前の設定を作成するには、次のようなコマンドラインを使用します:

BDS.EXE –rlean

これにより、標準の構成とは別に、レジストリ内にIDE向けのまったく新しい構成が作成されます。そのパラメータと -p の構成を組み合わせて単一のパーソナリティだけをアクティブにすることができます(特に、RAD製品をフルインストールした場合は)。

この手順のあとは、そのIDE自体で構成を編集することができます(例えば、不要な設計時パッケージの削除)。または、より積極的に削減するために、レジストリ内の設計時パッケージのリストを直接編集します。

もし、複数のIDEプラグインパッケージ(ランタイムコンポーネントパッケージというよりも)を使用している場合は、それらが余分なメモリを使用してIDEに影響を与えます。このことは、(大規模なプロジェクトの)コードを構文解析し、メモリ内に任意の情報を保持するあらゆるプラグインにとって確かにより重要です。

他のすべてが失敗しない限り、エンバカデロはあまり奨励しないより極端なオプションとして、IDEのコマンドラインに -noparser パラメータを追加することによってIDEの構造ペインを無効にするというものがあります。これは、コーディングを支援する多くのIDEの生産性向上機能に影響を与えますが、いくらかのメモリを節約し、おそらく安定性を向上させることになるでしょう。いくらかのユーザーはそれが機能したと報告しており、彼らは安定性を増すために機能とのトレードオフを受け入れました。

重要な機能を除去する極端な選択肢といえば、いくらかのユーザーはそれらに相当するIDEのライブラリ(Borland.Studio.Delphi.dll, Borland.Studio.Refactoring.dll, refactoride210.bpl を含む)を直接無効(名前を変更する)にしています。これはかなり極端なアプローチですが、わずかに問題があるものの、一部のユーザーがIDEを使用できるようにするため役立ってきました。

    6. アプリケーションのアーキテクチャの変更を考慮する

いくつかのシナリオでは、古くから使用されているアプリケーションのアーキテクチャはその年月を経ており、エンバカデロが本当に推奨するのはそれを更新することを検討することです。それはIDEのメモリ不足のエラーを克服するためだけではなく、アプリケーションをより堅牢にするためでもあります。

エンバカデロがここで参照している特定のシナリオは、メインアプリケーションによって使用される何十ものDLLを持つプロジェクトグループの場合です。これはWindowsの範囲で意味がありますが、同じクラスが複数のモジュールで複数回コンパイルされることがあるため、アプリケーション自体の中に重複したシンボルが多数存在するという問題を引き起こします。

実行時パッケージは、特に、モジュールのアプリケーションをより堅牢にするためにDelphiの初期に導入され、これは今日でも推奨されるアーキテクチャです。副作用として、同じユニットは一度だけコンパイルされますし(または、それらがライブラリパッケージ内にある場合は、実際にはコンパイルされない)、また、デバッガと他のすべてのシステムによって一度だけ(最大でも)必要とされます。最終的には、コンパイルされたコード内にはそれらのユニットのたった1つのコピー(多数ではなく)が存在します。

エンバカデロは、既存のDLLベースのアプリケーションのアーキテクチャを見直すことは重要な取り組みであると理解しています。しかし、その方向性はIDEのメモリ問題と併せて強く推奨するものの...、まずは先に示したその他の解決策を試した後でお願いします。

    7. Be aware of the “Growth by Generics”

アプリケーションコードに依存し、コンパイラおよびデバッガが使用するメモリの増加を引き起こすかもしれない別のシナリオは、ジェネリックデータ型の使用方法と関係があります。Object Pascalコンパイラが処理する方法では、同じジェネリックの定義に基づいて、多くの異なる型の生成を引き起こすことがあります。異なるモジュールでコンパイルされた全く同一の型である場合であってもです。エンバカデロはジェネリクスを削除することはまったく推奨しませんが、それとは逆に、考慮すべきいくつかの選択肢があります:

  • 核となるジェネリック型を定義したユニット間でのユニットの相互参照を避けるようにする
  • 可能であれば、同一の具象型を定義して使用する
  • 可能であれば、基底クラスでコードを共有するようにジェネリクスをリファクタリングし、ジェネリッククラスはそれを継承する

    8. R&Dは長期的な解決策に取り組んでいます

すでに述べたように、今回のメモリ不足問題のような問題のための長期的な解決策は、あらゆるすべてのメモリリークを追跡し、キャッシングアルゴリズムを改善してより効果的なものにし(およびメモリが少ないシナリオ向けにより柔軟にする)、コードの構文解析を最適化してスペースを確保し、IDEプロセスが利用可能なメモリの全体量を増やすことを検討することです。

ご存知のように、32-bitのアプリケーションであっても、標準的な2GBではなく理論的に利用可能な4GBのメモリすべてを使用するよう設定できます。エンバカデロでは内部的にはそれを試しましたが、ユーザーが期待する品質でそのような解決策を製品化するにはより多くの努力が必要です。RAD StudioのIDEは複数の言語や技術で記述されたモジュールを持っており、すべてのモジュールを広大なメモリアドレスの下で安全に使用するにはR&Dチームにもう少し時間が必要です。これらの問題を特定して修正するために今作業が行われており、そのオプションによりIDEの「メモリの天井」を大幅に増加させ、多くのメモリ不足のエラーのシナリオを削減します。

最終的には、IDEを64-bitアプリケーションへ移行するのが目標ですが、これには多大な努力が要ります。エンバカデロ(および多くのユーザー)はできる限り早い解決策を必要としていますが、それにはもっと時間がかかるでしょう。

    9. フィードバッグをお願いします

エンバカデロは、この文書にある解決策および回避策が役立つものかどうか(それらに下線を引いて同じことを他の人に提案することができます)、どのような障害に遭遇したのかを、似たようなメモリ不足エラーに遭遇したすべてのユーザーからフィードバックを募集しています。

この文書は、エンバカデロのサポートチームおよびR&Dチームからの更なる情報や、ユーザーの経験からの更なる情報を基に更新する予定です。RAD Studioのプロダクトマネージャチーム( marco.cantu@embarcadero.com )にEメールによるフィードバックをお願いします。