2

アプリケーションが連続して画像ファイルをロードすると、メモリを「スラッシングポイント」まで積極的に消費するという問題があります。たとえば、15MBのJPEGファイル(テスト目的で大きなファイルサイズ)を繰り返しロードおよびリリースする次のコードについて考えてみます。

NSURL *inputUrl = [NSURL URLWithString:@"file:///Users/me/Desktop/15MBjpeg.jpg"];  
for(int i=0; i<1000; i++) {  
    NSImage *image = [[NSImage alloc] initWithContentsOfURL:inputUrl];  
    [image release];  
}

十分な空きシステムメモリがあるため、最初の数日間は高速に動作しますが、最終的には、システムは私が「スラッシングポイント」と呼んでいるところにひざまずきます。ここでは、システムが次のイメージをロードするのに十分なメモリを解放するため、パフォーマンスが低下することになります。さらに、システムがこの占有されているが現在は使用されていないメモリを解放する必要があるため、他のアプリケーションの実行が遅くなります。

私にとって意味のあることは、メモリを割り当ててからシステムに解放させて、Activity Monitorの「RealMem」統計が連続したロード/リリースの反復でギガバイトに向かうのではなく、小さいままになるかどうかです。実際には、この時点で終わることはないかもしれませんが、いつでも必要な実際の常駐メモリが一般的に小さい場合、ActivityMonitorの「RealMem」統計が最終的に他のすべてのアプリケーションを超えるのは奇妙に思えます。当初、これはある種のメモリリークまたはキャッシュの問題だと思っていましたが、アプリケーションでの積極的なメモリ割り当てとシステムでの遅延メモリの解放に関連しているようです(OSにそのポリシーがある場合は、問題はありません。実際、それが機能する方法)。おそらく私は完全に何かが欠けています。

このホギングや積極的ではないメモリ使用量の動作なしに、画像を繰り返しロードするためのより良い方法はありますか?おそらく、アプリケーションのメモリフットプリントを強制的に下げる方法がありますか、または画像が同じオブジェクトまたはメモリ位置にロードされる方法についてより賢くする方法がありますか?私の目標は、画像を読み込んで処理し(サムネイルを取得し、画像形式を変更するなど)、メモリ内で画像を削除してから、もう一度実行することです。これらはすべて、この観察されたメモリの増加なしに行われます。

-

ファローアップ:

よろしくお願いします。NSAutoreleasePoolをラップすると、同じファイルをロードするときの反復的なメモリの増加が解決されます。

NSURL *inputUrl = [NSURL URLWithString:@"file:///Users/me/Desktop/15MBjpeg.jpg"];  
for(int i=0; i<1000; i++) {  
    NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
    NSImage *image = [[NSImage alloc] initWithContentsOfURL:inputUrl];  
    [image release];
    [apool drain];
}

ただし、イメージが解放された後(およびNSAutoreleasePoolがドレインされた後)にメモリが増加したままになる問題は解決されません。たとえば、15MBのJPEG画像をロードすると、「Real Mem」メモリは、8MBの定常状態から約25MBにジャンプし、そこにとどまります。(私のアプリケーションには、コピーしたforループのみを呼び出すメソッドへの有線IBActionを持つInterface Builderボタンしかありません)。forループが終了した後(または1つのイメージのみがロードおよびリリースされた場合)、「RealMem」統計は公称アプリケーションレベルのメモリ使用量に戻ると予想されます。

NSImage機能を呼び出すときに、バックグラウンドで他のものもロードできるのは合理的であるように思われます。これにより、メモリが増加する可能性があります。ただし、サイズの異なる画像(15MB、30MB、50MBなど)は、アプリケーションのメモリを比例的に増加させるため、そのような割り当て以上のものであると私は信じています。

さらに、別々の画像を連続してロードしようとすると(たとえば、15MBjpeg-1.jpg、15MBjpeg-2.jpgなど)、ロードされる新しい画像ごとにメモリが合成されることがあります。たとえば、2つのイメージが連続してロードされた場合、ロード/リリース後のアプリケーションの「Real Mem」メモリ使用量は約50MBになり、私の観察によれば、減少することはありません。この動作は、後続のイメージをロードするときにも継続するため、アプリケーションは、いくつかの大きなイメージをロードした後、数百MBの「RealMem」メモリ使用量に忍び寄ることができます。

興味深いことに、同じ画像を何度もリロードしても、定常状態のメモリは増加しません。これは、進行中のある種のキャッシュを示していますか?繰り返しになりますが、私の目標は、このメモリの増加なしに、いくつかの異なる画像ファイルをバッチ処理することです。前もって感謝します。

ああ、私はヒープショット分析の記事ATMを調べていますが、少なくとも進捗状況を投稿して、他の入力があるかどうかを確認したいと思いました。

-

フォローアップ#2

素晴らしい記事をありがとう。テストプログラムでInstrumentsAllocationsを実行しましたが、ヒープの増加は見つかりませんでした。参照されているブログ投稿と同様に、私のアプローチは、1)Interface Builderインターフェイスの[画像の読み込みと解放]ボタン(読み込み/解放動作を呼び出す)をクリックし、2)[ヒープのマーク]を数秒ごとに数回クリックすることでした。 Instruments Allocationsで、3)1)と2)を繰り返します。

このアプローチを使用すると、ヒープショットは時間の経過とともにヒープ成長列に0バイトを一貫して報告しました(15秒間5秒ごとに3クリック)。これは、ベースラインヒープショットから追加のメモリが割り当てられていないことを意味します。さらに、[統計]ペインには、[画像の読み込みと解放]ボタンをクリックするたびに13.25MBのMallocがありますが、ライブバイトは0バイトであり、完全に解放されていることを意味します。[画像の読み込みと解放]ボタンを3回クリックすると、画像の全体のバイト数は39.75MB(3 * 13.25MB)と報告されます。これは、39.75MBが割り当てられたが、ライブバイト数が0であるため完全に解放されたことを意味します。割り当てグラフ非常に迅速な操作であるため、すぐに急上昇し、すぐに元に戻ります。メモリの定常状態での使用にリークや成長がないことは、すべて理にかなっているようです。

しかし、「Real Mem」の統計がまだ高い場合はどうすればよいですか?ActivityMonitorはメモリの問題をデバッグするための標準ではないことを私は知っています。しかし、プログラムを閉じるまで「Real Mem」は高いままで、その後「Real Mem」はすべて「無料」カテゴリに戻ります。これは、私にとって奇妙なことです。

同じ方法でコードを複製するだけで、2つの画像(15MBjpeg-1.jpg、15MBjpeg-2.jpg)でこの同じアプローチをテストしましたが、ヒープの増加は見られませんでした。明らかに、より多くの割り当てが行われ、リリースされました。ただし、現在、「Real Mem」は、1つのイメージのみをロードおよびリリースする場合の約2倍の増加になります。また、テストプログラムの定常状態では減少しません。

さらに私にできることはありますか?試してみたい人のための単一の画像のロード/リリースのテストコードは次のとおりです(IBボタンをopenFilesに接続するだけです)。

#import "TestMemory.h" // only declares -(IBAction) openFiles:(id)sender;
@implementation TestMemory
-(IBAction) openFiles:(id)sender {
    NSURL *inputUrl = [NSURL URLWithString:@"file:///Users/me/Desktop/15MBjpeg.jpg"];
    NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
    NSImage *image = [[NSImage alloc] initWithContentsOfURL:inputUrl];
    [image release];
    [apool drain];
}
@end

読んでくれてありがとう。:)

-

フォローアップ#3

bbum、いいえガベージコレクションを有効にしていません(プロジェクト設定でGCがサポートされていませんに設定されています)。InstrumentsAllocationsのvmmapとVMTrackerバーを使用してメモリを調査しました。vmmapと同じ情報を報告するため、VMInstrumentsと言ったときにVMTrackerデータを意味していると思いました。[画像の読み込みと解放]ボタンを使用して単一の画像を開いた後、いくつかの重要なメモリ数には次のものが含まれます(VMトラッカーから)。

Type         %ofRes  ResSize  VirtSize Res%  %AllDirty  DirtySize
__TEXT       38%     33.84MB  80.45MB  42%   0%         0Bytes
*Dirty*     32%     28.23MB  114.99MB 24%   100%       17.11MB
MALLOC_LARGE 14%     13.25MB  13.25MB  100%  0%         4KB
Carbon       11%     9.86MB   9.86MB   100%  20%        3.46MB
VM_ALLOCATE  9%      8.43MB   48.17MB  18%   49%        8.43MB
...

興味深いことに、単一のイメージの後続のロード/リリースにより、 DirtyおよびVM_ALLOCATEタイプの「ResidentSize」が約0.3MB増加し、これらのタイプの「DirtySize」も時間の経過とともに増加します。(VM_ALLOCATEはDirtyのサブセットのようです)。他のタイプは、その後のロード/リリースで変更されるようには見えません。

このデータから何を取り除くべきか、またはプログラムにメモリを解放させるためにどのように使用できるのか、正確にはわかりません。VM_ALLOCATEタイプは解放されていないチャンクである可能性がありますが、それは単なる推測です。基盤となるNSImageinit実装の一部がイメージファイルのキャッシュを保存し、それを解放しない可能性はありますか?繰り返しになりますが、前述のように、同じイメージファイルの後続の各ロード/リリースは、最初のロード/リリースと比較して、リソース(CPU、ディスクグラインドなど)と実時間をほとんど消費しません。前もって感謝します。

4

1 に答える 1

2
  • バヴァリアスが言ったこと; で囲んでみましたかNSAutoreleasePool

  • これは、古典的なマイクロベンチマークです。それは確かに問題を示していますが、問題は実際には、ベンチマークが予想される実際のパターンから大きく逸脱しているため、バグがベンチマークにあることである可能性があります。

  • 効率的に設計されたアプリケーションでは、ディスクから同じイメージデータを複数回読み取ることはありません。

  • これは、ヒープショット分析の最有力候補です。


(フォローアップに感謝します;役に立ちました!)

説明する症状は、VMリーク(割り当てを行わずにアドレスを消費しているもの、たとえばマップトメモリ)またはプルーニングされていないキャッシュ(VM割り当てを含む)のように聞こえます。

  • GCを有効にしましたか?その場合、これは、GCしきい値がトリガーされていないことが原因である可能性があります。コレクターは、非GCからGCゾーンへのReallyLargeAllocationsをクレジットすることを知りません。コレクションを強制すると、この特定のエッジケースを回避できます。

  • VMインスツルメントを確認するか、コマンドラインでvm_mapを使用して、アプリ内のアドレス空間を消費しているものを確認してください。

于 2011-02-19T05:19:44.247 に答える