1

ここでかなり一般的な質問があります。誰があなたの記憶を盗んでいるかを見つけるために、一般的に何をしますか?

私はビデオ エンコーダーを持っていますが、セットアップは非常に複雑です。imagesコントローラーはコントローラーにあり、エンコーダーは別のコントローラーにあります。私は を求めて、images時には多くのレベルのコントローラーを通過するデリゲートを介してそれらを取得します。また、dispatch_asyncプロセスでいくつかの呼び出しを使用しています。画像は のスナップショットであり、UIViewで処理されますCoreGraphics、私は最終的なイメージを保持し、使用後に他のコントローラーで解放しています。すべてが正常に動作し、メモリは常に約 25Mb ですが、エンコードが完了するとメモリが非常に速く増加し、最大で 1 分で 25Mb から 330Mb になり、もちろんクラッシュします。ログを入れて、まだ画像を要求しているかどうかを確認しようとしましたが、問題はないようです。エンコーダーは期待どおりに停止します。エンコーダーはバックグラウンドで実行するように設定されています。

重要なことの 1 つは、リーク (またはリークが ARC で何も報告しないため割り当て) を見つけようとすると、アプリがすぐにクラッシュすることですが、メモリが原因ではないということです。どうにかしてディスパッチを台無しにしたのではないかと思います。また、機器に起因する遅延のために、指定された時間に何かを利用できない可能性があります。ただし、ログなしでこれを見つけるのも困難です。計測器でデバッグしているときにログを表示できますか?

役立つ情報をありがとう。

編集: 何もせずに allocs を使用してインストゥルメントを実行することに成功しました。クラッシュは一貫していないようです。インストルメント レポートを保存しました。メモリがどのように増加しているかがわかります。これを引き起こしている割り当てがあり、それをどのように読み取るかという質問が再開されると思います。ファイルはこちらhttp://ge.tt/1PF97Pj/v/0?c

4

2 に答える 2

2

ここでの問題は、 で「ビジー待機」していることです。adaptor.assetWriterInput.readyForMoreMediaDataつまり、タイトなループで何度も呼び出しています。これは、一般的に言えば、悪い習慣です。ヘッダーには、このプロパティは Key-Value Observable であると記載されているため、プロセス全体を進めるために、Key-Value 変更通知をリッスンするようにコードを再構築することをお勧めします。さらに悪いことに、AVAssetInputWriter動作方法によっては (実行ループ ベースかどうかはわかりません)、ここでのビジー待機の行為により、アセット入力ライターが実際の作業を実行できなくなる可能性があります実行ループを続行させるまで発生しない可能性のある作業が行われるのを待っているデッドロック。

今、あなたは自問しているかもしれません: ビジー待機はどのようにメモリ プレッシャを引き起こしているのでしょうか? 舞台裏で、readyForMoreMediaData呼び出すたびに自動解放されたオブジェクトが割り当てられるため、メモリ不足が発生しています。この値をビジー待機してタイトなループで何度もチェックするため、実行ループが自動解放プールをポップする機会がないため、ますます多くのオブジェクトが割り当てられ、解放されることはありません。(実際の割り当ての詳細については、以下を参照してください) この (不適切な) ビジー待機を継続したい場合は、次のようにしてメモリの問題を軽減できます。

BOOL ready = NO;
do {
    @autoreleasepool {
        ready = adaptor.assetWriterInput.readyForMoreMediaData;
    }
} while (!ready);

これにより、によって作成された自動解放されたオブジェクトはreadyForMoreMediaData、各チェックの後に解放されます。しかし、実際には、このコードを再構築してビジー待機を回避することで、長期的にははるかに優れたサービスを提供できます。絶対にビジーウェイトが必要な場合は、少なくともループの各パスで次のようなことを行うと、CPUusleep(500);それほどスラッシングしなくなります。しかし、忙しくしないでください-待ってください。

編集:Instrumentsからこれを理解する方法を理解したいと思っていたこともわかりました。説明してみましょう。あなたが投稿したファイルから始めて、私がしたことは次のとおりです。

上部ペインの [割り当て] 行をクリックしてから、[作成済み & まだ生きている] オプションを選択しました (オブジェクトが破壊されている場合、ヒープの成長は見られないためです)。次に、[オプション] で時間フィルターを適用しました。表示されている大きな「ランプ」で小さな範囲をドラッグします。この時点で、ウィンドウは次のようになります。 ここまでの楽器

ここで、非常によく似た 4K の malloc されたオブジェクトがリストにたくさんあることがわかります。これが喫煙銃です。次に、それらのいずれかを選択し、ウィンドウの右側のペインを展開してスタック トレースを表示します。この時点で、ウィンドウは次のようになります。 ここに画像の説明を入力

右側のパネルに、そのオブジェクトが作成されているスタック トレースが表示されます。これは で割り当てられていることがわかりAVAssetInputWriterますが、コードの最後のフレームの下 (視覚的には上) にある最初の関数は です-[AVAssetWriterInput isReadForMoreMediaData]。バックトレースには、これが自動解放されたオブジェクトに関連しているというヒントがあり、そのautoreleaseようなタイトなループに座っていると、標準の自動解放メカニズムが実行される機会がありません (つまり、現在のプールがポップされます)。

このスタックからの私の結論は、-[AVAssetWriterInput isReadForMoreMediaData], (おそらく_helper次のスタック フレーム内の関数) 内の何かが、[[foo retain] autorelease]結果を返す前に a を実行するということです。自動解放メカニズムは、自動解放プールがポップ/ドレインされるまで、自動解放されたすべてのものを追跡する必要があります。それらを追跡するために、「自動解放されるのを待っているもののリスト」にスペースを割り当てる必要があります。これは、これらが malloc ブロックであり、自動解放されたオブジェクトではない理由についての私の推測です。(つまり、割り当てられているオブジェクトはありませんが、プールがプッシュされてから発生したすべての自動解放操作を追跡するためのスペースだけがあります。このプロパティをタイトなループでチェックしているため、そのうちの多くがあります。 )

それが私が問題を診断した方法です。うまくいけば、それは将来あなたを助けるでしょう。

于 2013-06-14T12:14:27.250 に答える
0

私の質問に答えるために、dispatch_async呼び出しを削除するとメモリの問題は修正されますが、UI がブロックされてまったく良くありません。これらすべてを組み合わせる方法であるべきなので、ブロックしません。これが私のコードです

- (void) image:(CGImageRef)cgimage atFrameTime:(CMTime)frameTime {
//NSLog(@"> ExporterController image");
NSLog(@"ExporterController image atFrameTime %lli", frameTime.value);

if (!self.isInBackground && frameTime.value % 20 == 0) {
    dispatch_async(dispatch_get_main_queue(),^{
        //logo.imageView.image = [UIImage imageWithCGImage:cgimage];
        statusLabel.text = [NSString stringWithFormat:@"%i%%", frameCount/**100/self.videoMaximumFrames*/];
    });
}

if (cgimage == nil || prepareForCancel) {
    NSLog(@"FINALIZE THE VIDEO PREMATURELY cgimage == nil or prepareForCancel is YES");
    [self finalizeVideo];
    [logo stop];
    return;
}

// Add the image to the video file
//dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),^{
    NSLog(@"ExporterController buffer");
    CVPixelBufferRef buffer = [self pixelBufferFromCGImage:cgimage andSize:videoSize];
    NSLog(@"ExporterController buffer ok");
    BOOL append_ok = NO;
    int j = 0;

    while (!append_ok && j < 30) {

        if (adaptor.assetWriterInput.readyForMoreMediaData) {
            //printf("appending framecount %d, %lld %d\n", frameCount, frameTime.value, frameTime.timescale);
            append_ok = [adaptor appendPixelBuffer:buffer withPresentationTime:frameTime];
            if (buffer) CVBufferRelease(buffer);
            while (!adaptor.assetWriterInput.readyForMoreMediaData) {}
        }
        else {
            printf("adaptor not ready %d, %d\n", frameCount, j);
            //[NSThread sleepForTimeInterval:0.1];
            while(!adaptor.assetWriterInput.readyForMoreMediaData) {}
        }
        j++;
    }
    if (!append_ok) {
        printf("error appending image %d times %d\n", frameCount, j);
    }
NSLog(@"ExporterController cgimage alive");
    CGImageRelease(cgimage);
NSLog(@"ExporterController cgimage released");
//});


frameCount++;
if (frameCount > 100) {
    NSLog(@"FINALIZING VIDEO");
    //dispatch_async(dispatch_get_main_queue(),^{
        [self finalizeVideo];
    //});
}
else {
    NSLog(@"ExporterController prepare for next one");
    //dispatch_async(dispatch_get_main_queue(),^{
    //dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),^{
        NSLog(@"ExporterController requesting next image");
        [self.slideshowDelegate requestImageForFrameTime:CMTimeMake (frameCount, (int32_t)kRecordingFPS)];
    //});
}

}

于 2013-06-14T11:17:16.493 に答える