17

私のクラスは画像を画面外にレンダリングしています。すべての画像に対して同じコンテキストを何度も作成するのではなく、再利用するのCGContextは良いことだと思いました。メンバー変数を設定したので、次のようにnilの_imageContext場合にのみ、新しいコンテキストを作成する必要があります。_imageContext

if(!_imageContext)
    _imageContext = [self contextOfSize:imageSize];

それ以外の:

CGContextRef imageContext = [self contextOfSize:imageSize];

もちろん、CGContextもうリリースしません。

これらは私が行った唯一の変更であり、コンテキストを再利用するとレンダリングが約10ミリ秒から60ミリ秒に遅くなることがわかりました。私は何かを逃したことがありますか?もう一度描画する前に、コンテキストなどをクリアする必要がありますか?それとも、各画像のコンテキストを再作成する正しい方法ですか?

編集

最も奇妙な接続を見つけました。

アプリが画像のレンダリングを開始したときにアプリのメモリが信じられないほど増加する理由を探していたところ、レンダリングされた画像をに設定したところに問題があることがわかりましたNSImageView

imageView.image = nil;
imageView.image = [[NSImage alloc] initWithCGImage:_imageRef size:size];

ARCは以前のをリリースしていないようNSImageです。これを回避する最初の方法は、新しい画像を古い画像に描画することでした。

[imageView.image lockFocus];
[[[NSImage alloc] initWithCGImage:_imageRef size:size] drawInRect:NSMakeRect(0, 0, size.width, size.height) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
[imageView.image unlockFocus];
[imageView setNeedsDisplay];

メモリの問題はなくなり、CGContext-reuseの問題はどうなりましたか?コンテキストを再利用しない場合、10ミリ秒ではなく20ミリ秒かかります。もちろん、画像への描画には、単に設定するよりも時間がかかります。コンテキストの再利用にも60msではなく20msかかります。しかし、なぜ?接続できるかどうかはわかりませんが、NSImageView画像を描くのではなく設定するだけで、再利用に時間がかかる古い状態を再現できます。

4

2 に答える 2

15

私はこれを調査しました、そして私は同じ減速を観察します。カーネル呼び出しとユーザーランド呼び出しをサンプリングするように設定されたInstrumentsで見ると、原因がわかります。@RyanArteconaのコメントは正しい方向に進んでいました。CGSColorMaskCopyARGB8888_sse2回のテスト実行(1つはコンテキストを再利用し、もう1つは毎回新しいコンテキストを作成)でInstrumentsを最下位のユーザーランド呼び出しに集中させ、結果の呼び出しツリーを反転しました。コンテキストが再利用されていない場合、最も重いカーネルトレースは次のとおりです。

Running Time    Self            Symbol Name
668.0ms   32.3% 668.0           __bzero
668.0ms   32.3% 0.0              vm_fault
668.0ms   32.3% 0.0               user_trap
668.0ms   32.3% 0.0                CGSColorMaskCopyARGB8888_sse

CGSColorMaskCopyARGB8888_sseこれは、カーネルがそれらにアクセスすることによってフォールトインされているメモリのページをゼロにすることです。つまり、CGContextはVMページをビットマップコンテキストにマップしますが、カーネルは、誰かが実際にそのメモリにアクセスするまで、その操作に関連する作業を実際には実行しません。実際のマッピング/障害は、最初のアクセス時に発生します。

次に、コンテキストを再利用するときに最も重いカーネルトレースを見てみましょう。

Running Time            Self            Symbol Name
1327.0ms   35.0%        1327.0          bcopy
1327.0ms   35.0%        0.0              user_trap
1327.0ms   35.0%        0.0               CGSColorMaskCopyARGB8888_sse

これはカーネルコピーページです。私のお金は、@RyanArteconaが彼のコメントで話していた動作を提供する基本的なコピーオンライトメカニズムであることにかかっています。

CGBitmapContextCreateImageのAppleドキュメントでは、元のコンテキストでさらに描画が行われるまで、実際のビットコピー操作は行われないと記載されています。

私がテストしていた不自然なケースでは、再利用しないケースの実行には3392ミリ秒かかり、再利用のケースには4693ミリ秒かかりました(大幅に遅くなりました)。それぞれの場合の最も重いトレースを1つだけ考慮すると、カーネルトレースは、最初のアクセスで新しいページを埋めるのに668.0ミリ秒を費やし、イメージが参照を取得した後の最初の書き込みでコピーオンライトページに1327.0ミリ秒を費やすことを示しています。それらのページに。これは659msの違いです。この1つの違いだけで、2つのケース間のギャップの約50%を占めます。

したがって、少し詳しく説明すると、再利用されていないコンテキストの方が高速です。コンテキストを作成すると、ページが空であることがわかり、書き込み時にそれらのページを強制的にコピーする人が他にいないためです。彼ら。コンテキストを再利用する場合、ページは他の誰か(作成したイメージ)によって参照され、コンテキストの状態が変化したときにイメージの状態を保持するために、最初の書き込み時にコピーする必要があります。

デバッガーをステップスルーするときにプロセスの仮想メモリマップを確認することで、ここで何が起こっているかをさらに詳しく調べることができます。vmmapそのための便利なツールです。

実際には、おそらく毎回新しいCGContextを作成する必要があります。

于 2013-01-02T15:58:37.173 に答える
8

@ipmccの優れた徹底的な回答を補足するために、ここに説明の概要があります。

AppleCGBitmapContextCreateImageのドキュメントには次のように記載されています。

このCGImage関数によって返されるオブジェクトは、コピー操作によって作成されます。場合によっては、コピー操作は実際にはコピーオンライトのセマンティクスに従うため、ビットの実際の物理コピーは、ビットマップグラフィックスコンテキストの基になるデータが変更された場合にのみ発生します。

したがって、この関数が呼び出されると、画像の基になるビットがすぐにコピーされず、代わりにビットマップコンテキストが次に変更されるときにコピーされるのを待つ可能性があります。CGContext...このビットコピーは(コンテキストのサイズと色空間に応じて)コストがかかる可能性があり、コンテキストで次に呼び出される描画関数の一部としてInstrumentsプロファイルで偽装される可能性があります(ビットが強制的にコピーされる場合)。CGContextDrawImageこれはおそらくここで.kで起こっていることです

ただし、ドキュメントには次のように書かれています。

結果として、ビットマップグラフィックスコンテキストに追加の描画を実行する前に、結果の画像を使用して解放することをお勧めします。このようにして、データの実際の物理コピーを回避できます。

これは、コンテキストでさらに描画を行う必要があるときまでに、メモリ内で作成されたイメージ(つまり、ディスクに保存されている、ネットワーク経由で送信されているなど)の使用を終了する場合、イメージが必要になることはないことを意味します。物理的にコピーする必要があります。

TL; DR

ある時点でCGImageビットマップコンテキストから引き出す必要があり、コンテキストでそれ以上描画する前に、それへの参照を保持する必要がない場合(UIImageView'sイメージとして設定することを含む)、それはおそらく使用するのは良い考えCGBitmapContextCreateImageです。そうでない場合、画像はある時点で物理的にコピーされますが、これにはしばらく時間がかかる場合があり、毎回新しいコンテキストを使用する方がよい場合があります。

于 2013-01-02T20:32:54.940 に答える