12

FastMM4 メモリ マネージャーが組み込まれている Delphi 2009 を使用しています。

私のプログラムは、大きなデータセットを読み込んで処理します。データセットをクリアするか、プログラムを終了するたびに、すべてのメモリが正しく解放されます。メモリリークはまったくありません。

spenwarr の回答にある CurrentMemoryUsage ルーチンを使用して、「 Delphi プログラムで使用されるメモリを取得する方法 」で、処理中に FastMM4 で使用されるメモリを表示しました。

発生しているように見えるのは、プロセスとリリースサイクルごとにメモリの使用量が増加していることです。例えば:

データセットなしでプログラムを開始した後、1,456 KB が使用されました。

大規模なデータセットを読み込んだ後、218,455 KB が使用されました。

データセットを完全にクリアした後は 71,994 KB。この時点 (またはこの例の任意の時点) で終了すると、メモリ リークは報告されません。

同じデータセットを再度読み込んだ後、271,905 KB が使用されました。

データセットを完全にクリアした後の 125,443 KB。

同じデータセットを再度読み込んだ後、325,519 KB が使用されました。

データセットを完全にクリアした後の 179,059 KB。

同じデータセットを再度読み込んだ後、378,752 KB が使用されました。

プログラムのメモリ使用量は、ロード/クリア サイクルごとに約 53,400 KB 増加しているようです。タスク マネージャーは、これが実際に起こっていることを確認します。

FastMM4 は、オブジェクトが解放されたときに、プログラムのすべてのメモリをオペレーティング システムに戻すとは限らないため、さらに必要なときにメモリを保持できると聞いたことがあります。しかし、この継続的な成長は私を悩ませます。メモリ リークは報告されていないため、問題を特定できません。

なぜこれが起こっているのか、それが悪いのか、それについて私にできること、またはすべきことがあるかどうか、誰かが知っていますか?


答えてくれたdthorpeとMasonに感謝します。あなたは私が何かを見逃していることに気づかせてくれたものを考えさせ、試しさせました. そのため、詳細なデバッグが必要でした。

結局のところ、終了時にすべての構造が適切に解放されていました。しかし、実行中の各サイクル後のメモリ解放はそうではありませんでした。私の終了クリーンアップが正しくなかった場合、通常は終了時に検出可能なリークを引き起こすメモリブロックが蓄積されていましたが、そうでした。

サイクル間でクリアする必要のあるいくつかの StringLists とその他の構造がありました。以前のサイクルから残っている余分なデータを使用して、プログラムがどのように正しく機能したかはまだわかりませんが、機能しました。私はおそらくそれをさらに研究します。

この質問には回答済みです。ご協力いただきありがとうございます。

4

4 に答える 4

25

リンク先の CurrentMemoryUsage ユーティリティは、アプリケーションのワーキング セット サイズを報告します。ワーキング セットは、物理メモリ アドレスにマップされている仮想メモリ アドレス空間のページの総数です。ただし、これらのページの一部または多くには、実際のデータがほとんど格納されていない場合があります。したがって、ワーキング セットは、プロセスが使用しているメモリ量の「上限」です。使用のために予約されているアドレス空間の量を示しますが、実際にコミットされている (実際には物理メモリに常駐している) 量や、コミットされたページのうちアプリケーションが実際に使用している量を示すものではありません。

これを試してみてください: 数回のテストの実行後にワーキング セットのサイズが徐々に大きくなったのを確認したら、アプリケーションのメイン ウィンドウを最小化します。ほとんどの場合、ワーキング セットのサイズが大幅に減少します。なんで?これは、未使用のページを破棄してワーキング セットを最小限に縮小するアプリケーションを最小化するときに、Windows が SetProcessWorkingSetSize(-1) 呼び出しを実行するためです。アプリ ウィンドウが通常のサイズの場合、OS はこれを行いません。これは、ワーキング セットのサイズを頻繁に縮小すると、データがスワップ ファイルから強制的に再ロードされてパフォーマンスが低下する可能性があるためです。

さらに詳しく説明すると、Delphi アプリケーションはメモリをかなり小さなチャンク (ここでは文字列、そこにはクラス) に割り当てます。プログラムの平均メモリ割り当ては、通常、数百バイト未満です。このような小さな割り当てをシステム全体の規模で効率的に管理するのは難しいため、オペレーティング システムはそうしません。これは、特に 4k の仮想メモリ ページ サイズと 64k の仮想メモリ アドレス範囲の最小サイズで、大きなメモリ ブロックを効率的に管理します。

これはアプリケーションに問題をもたらします。通常、アプリケーションは小さなチャンクを割り当てますが、OS はかなり大きなチャンクでメモリを使い果たします。何をすべきか?答え: サブアロケートします。

Delphi ランタイム ライブラリのメモリ マネージャと FastMM 代替メモリ マネージャ(および地球上のほぼすべての他の言語またはツールセットのランタイム ライブラリ)は、どちらも 1 つのことを行うために存在します。つまり、OS からの大きなメモリ ブロックを、応用。すべての小さなブロックがどこにあるか、どれくらい大きいか、「リーク」しているかどうかを追跡するには、オーバーヘッドと呼ばれるメモリも必要です。

大量のメモリ割り当て/割り当て解除の状況では、割り当てたものの 99% を割り当て解除する状況が発生する可能性がありますが、プロセスのワーキング セットのサイズは、たとえば 50% しか縮小しません。なんで?ほとんどの場合、これはヒープの断片化が原因です。Delphi メモリ マネージャが OS から取得し、内部で分割した大きなブロックの 1 つで、メモリの小さなブロックがまだ使用されています。使用されるメモリの内部カウントは小さい (たとえば 300 バイト) ですが、ヒープ マネージャーがそれが入っている大きなブロックを OS に解放するのを妨げているため、その小さな 300 バイト チャンクのワーキング セットの寄与は 4k (または仮想ページであるか仮想アドレス空間であるかに応じて64kです-思い出せません)。

数メガバイトの小さなメモリ割り当てを含む大量のメモリを集中的に使用する操作では、ヒープの断片化が非常に一般的です。特に、メモリを集中的に使用する操作に関連しないものへのメモリ割り当てが、大きなジョブと同時に行われている場合はなおさらです。たとえば、80MB のデータベース オペレーションを実行すると、進行中にリストボックスにステータスも出力される場合、ステータスを報告するために使用される文字列は、データベース メモリ ブロックの間のヒープに分散されます。データベースの計算で使用されるすべてのメモリ ブロックを解放すると、リストボックスの文字列はまだ存在します (使用中、失われていません)。

ウィンドウを最小化するトリックを試して、ワーキング セットが減少するかどうかを確認してください。その場合、ワーキング セット カウンタによって返される数値の明らかな「重大度」を割り引くことができます。大規模な計算操作の後に SetProcessWorkingSetSize への呼び出しを追加して、使用されなくなったページを削除することもできます。

于 2010-04-02T23:51:22.740 に答える
1

どのような種類のデータセットを使用していますか? Delphi で完全に実装されている場合 (Midas などの別のメモリ マネージャーを使用して他のコードを呼び出していない場合)、意図的にデータセットをリークしてみることができます。

データセットはフォーム上にあり、フォームがそのコンポーネントをクリアすると自動的に解放されると仮定します。MyDataset := nil;フォームの OnDestroyを入れてみてください。これにより、データセットがリークし、データセットが所有するすべてのものも確実にリークされます。 2回ロードした後にもう一度ロードしてみて、リークレポートを比較し、それが何か役立つかどうかを確認してください.

于 2010-04-02T23:55:35.873 に答える
0

あなたはメモリを半分リークしています。明らかに。プログラムの実行中にメモリリークが発生していますが、プログラムを閉じると、データセットが適切に解放されるため、FastMM は (正当に) レポートしません。

詳細については、これを参照してください:私のプログラムはメモリを解放しません。なんで?

于 2010-12-19T17:53:49.057 に答える