大型自定义 UIView - CABackingStoreUpdate 性能

Posted

技术标签:

【中文标题】大型自定义 UIView - CABackingStoreUpdate 性能【英文标题】:Large custom UIView - CABackingStoreUpdate Performance 【发布时间】:2013-03-03 00:26:38 【问题描述】:

为什么在 iOS 4.3.5 上,“大型”(960 x 1380) 自定义 UIView 执行 CABackingStoreUpdate 的效率如此之低,我该如何提高绘图操作的性能?

不完全确定我的意思?继续阅读...

注意:

随着我对这个问题的理解不断发展,这个问题也在不断发展。因此,问题本身是相似的,但自首次提出问题以来,以下正文中的代码示例和基本细节/推理发生了显着变化。

上下文

我有一个非常基本的应用程序(底部的代码),它在自定义 UIView 的 drawRect: 方法中绘制单个省略号。应用程序演示了当绘制的椭圆大小保持不变但自定义 UIView 的大小变大时的性能差异:

我使用不同大小的自定义 UIView 在运行 ios 4.3.5 的第 4 代 iPod 和运行 iOS 5.1.1 的第 1 代 iPad 上多次运行该应用程序。 下表显示了从时间分析器仪器获取的结果:

以下仪器轨迹显示每个设备的两个极端的详细信息:

iOS 5.1.1 -(自定义 UIView 大小 320 x 460)

iOS 5.1.1 -(自定义 UIView 尺寸 960 x 1380)

iOS 4.3.5 -(自定义 UIView 大小 320 x 460)

iOS 4.3.5 -(自定义 UIView 大小 960 x 1380)

正如您所见(希望),在 4 个案例中的 3 个案例中,我们得到了我们期望的结果:大部分时间都花在了执行自定义 UIViews drawRect: 方法上,每个案例都保持 10fps。 但是第四个案例显示了应用程序在仅绘制单个形状时难以保持 7fps 的性能下降。在 UIView 的 CALayer 的显示方法中,大部分时间都花在了复制内存上,具体来说:

[CALayer 显示] > [CALayer _display] > CABackingStore 更新 > 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 的人。虽然为了简单起见,我在演示中使用了改变颜色的椭圆(非常适合核心动画的任务),但我实际想要执行的绘图操作是大量随时间扩展的线条,这是一个绘图任务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

【问题讨论】:

真的有必要使用NSTimer吗?你能不改用Core Animation吗?它是为这样的事情而设计的,比 NSTimers 等要好得多。 developer.apple.com/library/mac/ipad/#documentation/Cocoa/… 我知道我会错过原始问题中的一部分信息,抱歉@occulus。我尽可能多地使用 Core Animation 来完成,但在我的实际应用程序中,我必须使用 Quartz 2D,因为我需要执行混合操作。 @ElliottPerry 认为 Quartz 2D 是基于 OpenGL 的,使用着色器和 GL_BLEND_MODE 的组合,您在 Quartz 中所做的任何事情应该在 OpenGL 中都是可能的。 @RichardJ.RossIII 我不怀疑我想执行的每一个绘图操作都可以通过 OpenGL 实现,但我犹豫要不要实现这一点,因为我使用 Quartz 2D 遇到了一个障碍.我们都知道 OpenGL 的权衡(更快的性能以获得额外的复杂性)并且我们都知道单独使用 Quartz 2D 和 Core Animation 并不能达到合理的性能,你不得不直接使用 OpenGL ......但我不要认为这一点是在 960x1380 UIView 上绘制单个省略号。我想知道为什么这么简单的操作性能很差。 【参考方案1】:

由于第 4 代 iPod Touch 和第 1 代 iPad 具有相似的硬件(相同数量的内存/相同的 GPU),这表明您看到的问题是由于 iOS4 中未优化的代码路径造成的。

如果您查看导致 iOS4 上(负)性能峰值的视图的大小,它们的一侧都比 1024 长。最初 1024x1024 是 UIView 的最大尺寸,尽管此限制已被取消比这更大的视图完全有可能只在 iOS5 及更高版本中变得高效。

我推测您在 iOS4 中看到的过多内存复制是由于 UIKit 为大型 UIView 使用了全尺寸内存缓冲区,但随后必须复制适当大小的图块才能合成它们;并且在 iOS5 和更高版本中,他们要么取消了对可以合成的图块大小的任何限制,要么更改了 UIKit 为如此大的 UIView 呈现的方式。

就解决 iOS4 上的这个瓶颈而言,您可以尝试使用较小的 UIView 平铺您想要覆盖的区域。如果您将其结构为:

父视图 - 包含绘图和事件相关代码 平铺视图 1 - 包含 drawRect ... 平铺视图 n - 包含 drawRect

在每个平铺视图中,您可以要求父视图在适当调整图形上下文的变换后渲染其内容。这意味着您不必更改绘图代码,它只会被多次调用(这会产生少量开销,但请记住每次调用将仅绘制整个视图的一部分)。

请注意,父视图没有 drawRect 方法很重要 - 否则 UIKit 会认为您想直接在其中绘制,它会创建一个后备存储,从而使您回到相同的情况。

您还可以查看 CATiledLayer - 它会为您进行平铺,但是是异步的;这意味着您的绘图代码等必须处理从一个或多个后台线程执行。

【讨论】:

未优化的代码路径是我的思路,但我没有确凿的证据支持它。此后,我让 Apple 代表在 Apple developer forums 中证实了您的回答。我不知道之前强加的 UIView 最大尺寸。感谢您花时间向我解释这一点并提出可行的解决方法。这对我有很大帮助,我很感激。【参考方案2】:

正如您所观察到的,时间主要用于传输一些数据。我认为在 iOS 4.3.5 中,CoreGraphics 不使用 GPU 和图形内存来实现 CGContextFillEllipseInRect 等原始绘图功能......

然后每次你需要绘制一些东西时,它会被 CPU 绘制在主内存中以计算所需的所有内容,然后复制到图形内存中。这当然需要很长时间,因为公共汽车很慢。

我猜从 iOS 5. 或 5.1 开始,原始绘图函数会调用一些 GPU 着色器(GPU 内的程序),然后所有繁重的工作都在那里完成。

然后只有少数数据(参数和程序代码)从主 RAM 内存传输到图形内存。

【讨论】:

以上是关于大型自定义 UIView - CABackingStoreUpdate 性能的主要内容,如果未能解决你的问题,请参考以下文章

自定义 UITableViewCell 中的自定义 UIView

在自定义 UIView 类中从 UIView 快速创建 IBoutlet [重复]

UIView 类别 - 自定义方法不返回 UIView

在 Storyboard 中设计的自定义 UITableViewCell 中的自定义 UIView

为啥自定义 xib 需要 UIView 属性

Xib 自定义 UIView 的实例化