12

なぜ、iOS 4.3.5 で「大規模な」(960 x 1380) カスタム UIView が CABackingStoreUpdate を非効率的に実行するのですか? また、描画操作のパフォーマンスを改善するにはどうすればよいですか?

私が何を意味するのか完全にはわかりませんか?読む...

ノート:

この問題に対する私の理解が進むにつれて、この質問も進化してきました。その結果、質問自体は似ていますが、次のテキスト本文のコード例と基本的な詳細/推論は、質問が最初に尋ねられてから大幅に変更されました。

環境

カスタム UIView の drawRect: メソッドで 1 つの省略記号を描画する非常に基本的なアプリケーション (下部のコード) があります。このアプリケーションは、描画される楕円のサイズは同じままで、カスタム UIView のサイズが大きくなった場合のパフォーマンスの違いを示しています。

小さな UIView

大きな UIView

iOS 4.3.5 を実行する iPod 第 4 世代と iOS 5.1.1 を実行する iPad 第 1 世代の両方で、さまざまなサイズのカスタム UIView を使用して一連のアプリケーションを実行しました。
次の表は、時間プロファイラー機器から取得した結果を示しています。

時間プロファイラーの結果

次のインストゥルメント トレースは、各デバイスの両極端の詳細を示しています。

iOS 5.1.1 - (カスタム UIView サイズ 320 x 460)

iOS 5.1.1 - カスタム UIView サイズ 320 x 460

iOS 5.1.1 - (カスタム UIView サイズ 960 x 1380)

iOS 5.1.1 - カスタム UIView サイズ 960 x 1380

iOS 4.3.5 - (カスタム UIView サイズ 320 x 460)

iOS 4.3.5 - カスタム UIView サイズ 320 x 460

iOS 4.3.5 - (カスタム UIView サイズ 960 x 1380)

iOS 4.3.5 - カスタム UIView サイズ 960 x 1380

(願わくば) 4 つのケースのうち 3 つのケースでわかるように、期待どおりの結果が得られました。ほとんどの時間は、カスタム UIViews drawRect: メソッドの実行に費やされ、それぞれが 10fps を保持していました。
しかし、4 番目のケースでは、アプリケーションが単一の図形を描画するだけで 7 fps を維持するのに苦労しており、パフォーマンスが低下しています。ほとんどの時間は、UIView の CALayer の表示方法でメモリをコピーするのに費やされました。具体的には次のとおりです。

[CALayer 表示] >
[CALayer _display] >
CABackingStoreUpdate >
CA::Render::ShmemBitmap::copy_pixels(CA::Render::ShmemBitmap const*, CGSRegionObject*) >
memcpy$VARIANT$CortexA8

ここで何か深刻な問題があることを数字から理解するのに、天才は必要ありません。サイズが 960 x 1380 のカスタム UIView を使用すると、iOS 4.3.5 は、ビュー全体のコンテンツを描画するよりも、メモリをコピーするのに 4 倍以上の時間を費やします。

質問

ここで、文脈を考慮して、もう一度質問します。

なぜ、iOS 4.3.5 で「大規模な」(960 x 1380) カスタム UIView が CABackingStoreUpdate を非効率的に実行するのですか? また、描画操作のパフォーマンスを改善するにはどうすればよいですか?

どんな助けでも大歓迎です。

この質問はApple Developer forumsにも投稿しました。

本物

さて、明らかに、この質問のために、実際の問題を最も単純な再現可能なケースに減らしました。私は実際に、UIScrollView 内にある 960 x 1380 のカスタム UIView の一部をアニメーション化しようとしています。

私は、Quartz 2D で望むパフォーマンスのレベルを達成していないときに、OpenGL ES に向けて誰かを誘導する誘惑に感謝しますが、その道をたどる人には、少なくとも、Quartz 2D がパフォーマンスに苦労している理由について説明を提供するようお願いしますiOS 5.1.1 では問題ない iOS 4.3.5 での最も基本的な描画操作。ご想像のとおり、この重要なケースのためにすべてを書き直すという考えには、私は興奮していません。これは、Core Animation の使用を提案する人々にも当てはまります。わかりやすくするために、デモでは色を変える楕円形 (Core Animation に最適なタスク) を使用しましたが、実際に実行したい描画操作は、時間の経過とともに拡大する大量の線、描画タスクです。 Quartz 2D は (パフォーマンスが高い場合に) 理想的です。さらに、もう一度、

コード

TViewController.m (標準ビュー コントローラの実装)

#import "TViewController.h"
#import "TCustomView.h"

// VERSION 1 features the custom UIView the same size as the screen.
// VERSION 2 features the custom UIView nine times the size of the screen.
#define VERSION 2


@interface TViewController ()
@property (strong, nonatomic) TCustomView *customView;
@property (strong, nonatomic) NSTimer *animationTimer;
@end


@implementation TViewController

- (void)viewDidLoad
{
    // Custom subview.
    TCustomView *customView = [[TCustomView alloc] init];
    customView.backgroundColor = [UIColor whiteColor];
#if VERSION == 1
    customView.frame = CGRectMake(0.0f, 0.0f, 320.0f, 460.0f);
#else
    customView.frame = CGRectMake(0.0f, 0.0f, 960.0f, 1380.0f);
#endif

    [self.view addSubview:customView];

    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
    [customView addGestureRecognizer:singleTap];

    self.customView = customView;
}

#pragma mark - Timer Loop

- (void)handleTap:(UITapGestureRecognizer *)tapGesture
{
    self.customView.value = 0.0f;

    if (!self.animationTimer  || !self.animationTimer.isValid) {
        self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(animationLoop) userInfo:nil repeats:YES];
    }
}

#pragma mark - Timer Loop

- (void)animationLoop
{
    // Update model here. For simplicity, increment a single value.
    self.customView.value += 0.01f;

    if (self.customView.value >= 1.0f)
    {
        self.customView.value = 1.0f;
        [self.animationTimer invalidate];
    }

    [self.customView setNeedsDisplayInRect:CGRectMake(0.0f, 0.0f, 320.0f, 460.0f)];
}

@end

-

TCustomView.h (カスタム ビュー ヘッダー)

#import <UIKit/UIKit.h>

@interface TCustomView : UIView
@property (assign) CGFloat value;
@end

-

TCustomView.m (カスタム ビューの実装)

#import "TCustomView.h"

@implementation TCustomView

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    // Draw ellipses.
    CGContextSetRGBFillColor(context, self.value, self.value, self.value, 1.0f);
    CGContextFillEllipseInRect(context, rect);

    // Draw value itself.
    [[UIColor redColor] set];
    NSString *value = [NSString stringWithFormat:@"%f", self.value];
    [value drawAtPoint:rect.origin withFont:[UIFont fontWithName:@"Arial" size:15.0f]];
}

@end
4

2 に答える 2

5

iPod Touch4thGenとiPad1stGenはどちらも同様のハードウェア(同じ量のメモリ/同じGPU)を備えているため、iOS4で最適化されていないコードパスが原因で問題が発生している可能性があります。

iOS4で(負の)パフォーマンススパイクを引き起こすビューのサイズを見ると、どちらも片側が1024より長くなっています。元々は1024x1024がUIViewの最大サイズでしたが、この制限は解除されましたが、完全に解除されました。これよりも大きいビューは、iOS5以降でのみ効率的になる可能性があります。

iOS4で見られる過剰なメモリコピーは、UIKitが大きなUIViewにフルサイズのメモリバッファを使用しているためだと思いますが、合成する前に適切なサイズのタイルをコピーする必要があります。また、iOS5以降では、合成できるタイルのサイズに関する制限が削除されたか、UIKitがそのような大きなUIViewに対してレンダリングする方法が変更されました。

iOS4でのこのボトルネックを回避するという点では、カバーしたい領域を小さなUIViewでタイリングしてみることができます。次のように構成する場合:

  親ビュー-図面とイベント関連のコードが含まれています
    タイルビュー1-drawRectが含まれています
    ..。
    タイルビューn-drawRectが含まれています

各タイルビューでは、グラフィックスコンテキストの変換を適切に調整した後、親ビューにそのコンテンツをレンダリングするように依頼できます。これは、描画コードを変更する必要がないことを意味します。複数回呼び出されるだけです(これにはわずかなオーバーヘッドがありますが、各呼び出しはビュー全体の一部のみを描画することに注意してください)。

親ビューにdrawRectメソッドがないことが重要であることに注意してください。そうしないと、UIKitは直接描画したいと考え、バッキングストアを作成して、同じ状況に戻します。

調べることができるCATiledLayerもあります。これはタイリングを行いますが、非同期です。つまり、描画コードなどは、1つ以上のバックグラウンドスレッドからの実行を処理する必要があります。

于 2013-03-09T10:02:08.257 に答える
0

ご覧のとおり、時間は主に一部のデータの転送に費やされます。iOS 4.3.5では、CoreGraphicsはGPUとグラフィックメモリを使用して、CGContextFillEllipseInRectなどのプリミティブな描画関数を実装していないと思います...

次に、何かを描画する必要があるたびに、CPUを使用してメインメモリに描画され、必要なすべてが計算されてから、グラフィックメモリにコピーされます。バスはかなり遅いので、もちろんそれは長い時間がかかります。

iOS 5.または5.1以降、プリミティブ描画関数はいくつかのGPUシェーダー(GPU内のプログラム)を呼び出し、その後、すべての重い作業がそこで行われると思います。

次に、少数のデータ(パラメータとプログラムコード)のみがメインRAMメモリからグラフィックメモリに転送されます。

于 2013-03-09T18:26:48.247 に答える