iOS / Core-Animation:性能调优

时间:2011-02-15 12:26:28

标签: performance ios optimization core-animation

我在iPad上运行了自己的应用。但它的表现非常糟糕 - 我的速度低于15fps。任何人都可以帮我优化吗?

它基本上是一个包含12个按钮(来自UIControl)的滚轮(源自UIView)。 image of control

当用户旋转时,按钮会动态扩展和收缩(例如,12点钟位置的按钮应始终是最大的)

所以我的车轮包含:

- (void) displayLinkIsCallingBack: (CADisplayLink *) dispLink 
{
    :
    // using CATransaction like this goes from 14fps to 19fps
    [CATransaction begin];
    [CATransaction setDisableActions: YES];

    // NEG, as coord system is flipped/fucked
    self.transform = CGAffineTransformMakeRotation(-thetaWheel);

    [CATransaction commit];

    if (BLA)
        [self rotateNotch: direction];  
}

...从最近的触摸输入计算车轮的新旋转。这里已经存在一个性能问题,我在一个单独的线程中追求:iOS Core-Animation: Performance issues with CATransaction / Interpolating transform matrices

此例程还会检查滚轮是否已完成另外1/12旋转,如果是,则指示所有12个按钮调整大小:

// Wheel.m
- (void) rotateNotch: (int) direction
{
    for (int i=0; i < [self buttonCount] ; i++)
    {
            CustomButton * b = (CustomButton *) [self.buttons objectAtIndex: i];

    // Note that b.btnSize is a dynamic property which will calculate
    // the desired button size based on the button index and the wheels rotation.
            [b resize: b.btnSize];
    }
}

现在对于实际的大小调整代码,请在button.m:

// Button.m

- (void) scaleFrom: (float) s_old
                to: (float) s_new
              time: (float) t
{
    CABasicAnimation * scaleAnimation = [CABasicAnimation animationWithKeyPath: @"transform.scale"];

    [scaleAnimation setDuration:  t ];
    [scaleAnimation setFromValue:  (id) [NSNumber numberWithDouble: s_old] ];
    [scaleAnimation setToValue:  (id) [NSNumber numberWithDouble: s_new] ];
    [scaleAnimation setTimingFunction:  [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut] ];
    [scaleAnimation setFillMode: kCAFillModeForwards];
    scaleAnimation.removedOnCompletion = NO;

    [self.contentsLayer addAnimation: scaleAnimation 
                              forKey: @"transform.scale"];

    if (self.displayShadow && self.shadowLayer)
        [self.shadowLayer addAnimation: scaleAnimation 
                                forKey: @"transform.scale"];    

    size = s_new;
}

// - - - 

- (void) resize: (float) newSize
{
    [self scaleFrom: size
                 to: newSize
               time: 1.];
}

我想知道问题是否与多个transform.scale操作排队的开销有关 - 每个按钮调整大小需要一整秒才能完成,如果我快速旋转滚轮,我可能每秒旋转几转;这意味着每个按钮每秒调整大小24次。

**创建按钮的图层**

我猜的最后一块拼图是看看按钮的内容层。但我试过了

contentsLayer.setRasterize = YES;

应该有效地将其存储为位图。所以设置代码实际上是动态调整12位图的大小。

我无法相信这会使设备超出其极限。然而,核心动画乐器却告诉我;当我旋转滚轮时(通过将我的手指拖动到圆圈中),它报告〜15fps。

这不好:我最终需要在每个按钮内放置一个文本层,这将进一步拖累性能(...除非我使用上面的.setRasterize设置,在这种情况下它应该是相同的)。

一定有些事情我做错了!但是什么?

编辑:这是负责生成按钮内容层的代码(即带阴影的形状):

- (CALayer *) makeContentsLayer
{
    CAShapeLayer * shapeOutline = [CAShapeLayer layer];
    shapeOutline.path = self.pOutline;

    CALayer * contents = [CALayer layer];

    // get the smallest rectangle centred on (0,0) that completely contains the button
    CGRect R = CGRectIntegral(CGPathGetPathBoundingBox(self.pOutline)); 
    float xMax = MAX(abs(R.origin.x), abs(R.origin.x+R.size.width));
    float yMax = MAX(abs(R.origin.y), abs(R.origin.y+R.size.height));
    CGRect S = CGRectMake(-xMax, -yMax, 2*xMax, 2*yMax);
    contents.bounds = S; 

    contents.shouldRasterize = YES; // try NO also

    switch (technique)
    {
        case kMethodMask:
            // clip contents layer by outline (outline centered on (0, 0))
            contents.backgroundColor = self.clr; 
            contents.mask = shapeOutline;
            break;

        case kMethodComposite:
            shapeOutline.fillColor = self.clr;
            [contents addSublayer: shapeOutline];
            self.shapeLayer = shapeOutline;
            break;

        default:
            break;
    }

    if (NO)
        [self markPosition: CGPointZero
                   onLayer: contents ];

    //[self refreshTextLayer];

    //[contents addSublayer: self.shapeLayerForText];

    return contents;
}

正如你所看到的,我正在尝试一切可能的方法,我正在尝试两种方法来制作形状,另外我正在尝试切换.shouldRasterize

**妥协UI设计以获得可容忍的帧速率**

编辑:现在我已经尝试禁用动态调整大小行为,直到轮子进入新位置,并设置wheel.setRasterize = YES。所以它有效地旋转了一个预先渲染的UIView(它无疑占据了大部分屏幕)在手指下面(它很高兴地做了~~ 60fps),直到轮子停下来,此时它执行这个滞后的大小调整动画( @&LT; 20FPS)

虽然这给出了可以容忍的结果,但我不得不以这种方式牺牲我的UI设计。我确信我一定做错了。

编辑:我刚试过手动调整按钮大小的实验;即在每个按钮中放置一个显示链接回调,并动态计算该给定帧的预期大小,使用与我对方向盘相同的CATransaction显式禁用动画,设置新的变换矩阵(从预期大小生成的缩放变换)。添加到此我已设置按钮内容层shouldRasterize = YES。所以它应该简单地将每帧12个位图缩放到一个本身正在旋转的UIView上。令人惊讶的是,这已经慢了,它甚至会让模拟器停下来。它肯定比使用核心动画的动画功能自动慢10倍。

5 个答案:

答案 0 :(得分:15)

我没有开发iPad应用程序的经验,但我确实有一些优化视频游戏。所以,我无法给出确切的答案,但我想提供一些优化建议。

  

不要猜。简介。

您似乎在尝试进行更改而不分析代码。改变一些可疑代码并交叉手指并不真正奏效。您应该通过检查每项任务的long采用方式以及运行所需often的方式来描述您的代码。尝试分解您的任务并将分析代码放在衡量timefrequency的位置。如果您可以测量每个任务使用memory的数量,或者有多少其他系统资源,那就更好了。根据证据找出你的瓶颈,而不是你的感觉。

对于您的具体问题,您认为当您的调整大小工作开始时,程序会变得非常慢。但是,您确定吗?它可能是别的东西。在得到实际的分析数据之前,我们不确定。

  

最小化问题区域并在进行更改前衡量实际效果。

分析后,您有一个候选人可以解决您的瓶颈问题。如果您仍然可以将原因拆分为几个小任务,那么请执行此操作并对其进行分析,直到您无法再将其拆分为止。然后,尝试通过重复运行几千次来测量它们的精确性能。这很重要,因为您需要在进行任何更改之前了解性能(速度和空间),以便将其与未来的更改进行比较。

针对您的具体问题,如果确实需要调整大小,请尝试检查其执行方式。执行一次调整大小调用需要多长时间?您需要多长时间调整一次工作来完成工作?

  

改进它。怎么样?这取决于。

现在,您遇到了有问题的代码块及其当前性能。你现在必须改进它。怎么样?好吧,这真的取决于问题所在。您可以搜索better algorithms,如果您可以延迟计算直到确实需要执行,则可以执行lazy fetching,或者如果您执行相同的操作,则可以通过缓存某些数据来执行over-eager evaluation太频繁了。你越了解原因,就越容易改进它。

针对您的具体问题,可能只是OS ui功能的限制。通常,调整大小按钮不仅仅是调整按钮本身,还会invalidates整个区域或其父窗口小部件,以便可以正确呈现每个UI内容。调整大小按钮可能很昂贵,如果是这种情况,您可以通过简单地使用基于图像的方法而不是基于OS ui的系统来解决问题。如果OS API中的图像操作不够,您可以尝试使用OpenGL。但是,我们再次知道,直到我们真正尝试它们并profile这些替代品。祝好运! :)

答案 1 :(得分:6)

在没有阴影的情况下尝试它,看看是否能提高性能。我想它会大大改善它。然后我会研究使用CALayer的阴影路径来渲染阴影。这将大大提高阴影渲染性能。

来自去年WWDC的Apple核心动画视频有很多关于提高核心动画性能的绝佳信息。

顺便说一下,我现在正在制作更复杂的动画,即使在较旧的iPhone 3G上也能很好地工作。硬件/软件非常强大。

答案 2 :(得分:1)

您应该在OpenGL中重新执行此操作

答案 3 :(得分:1)

你是否在你的图层上使用阴影这是性能问题的原因,那么你有2个选项AFAIK:   - 以这种方式设置shadowPath,CA不必每次都计算它   - 删除阴影并使用图像替换

答案 4 :(得分:0)

我知道这个问题很老,但仍然是最新的。 CoreAnimation (CA)只是 OpenGL 的包装 - 或者可能围绕 Metal 。实际上,图层是在矩形上绘制的,并且动画是使用3D变换表示的。由于所有这些都由GPU处理,它应该超快......但它不是。整个CA子系统看起来相当复杂,在AppKit / UIKit和3D世界之间进行翻译比看起来更难(如果你曾试图自己编写这样的包装器,你知道它有多难)。对于程序员来说,CA提供了一个超级简单易用的界面,但这种简单性需要付出代价。到目前为止,我尝试优化非常慢的CA都是徒劳的;你可以加快一点速度,但在某些时候你必须重新考虑你的方法:要么CA足够快就能完成你的工作,要么你需要停止使用CA并使用经典的视图绘图自己实现所有动画(如果是CPU)可以处理这个问题)或者使用3D API自己实现动画(然后GPU会这样做),在这种情况下,您可以决定3D世界如何与应用程序的其他部分进行交互;价格要写的代码要多得多,要使用的API要复杂得多,但结果最终会说明一切。

尽管如此,我还想提供一些关于加速CA的一般提示:

  • 每次你&#34;画&#34;对于图层或将内容加载到图层(新图像)中,需要更新支持该图层的纹理数据。每个3D程序员都知道:更新纹理非常昂贵。不惜一切代价避免这种情况。
  • 不要使用巨大的图层,好像图层太大而不能直接由GPU作为单个纹理处理,它们被分割成多个纹理,仅此一项就会使性能变差。
  • 不要使用太多层,因为GPU可以在纹理上花费的内存量通常是有限的。如果你需要更多的内存而不是那个限制,纹理会被换出(从GPU内存中移除以为其他纹理腾出空间,稍后在需要绘制时添加回来)。请参阅上面的第一个提示,这种交换非常昂贵
  • 不要重新绘制不需要重绘的内容,而是将其缓存到图片中。例如。绘制阴影和绘制渐变都 utlra昂贵,通常很少会改变。因此,不是每次都使用CA绘制它们,而是将它们绘制到一个图层或将它们绘制到图像并将该图像加载到CALayer,然后将图层放置在您需要的位置。监视何时需要更新它们(例如,如果对象的大小已更改),则重新绘制它们一次并再次缓存结果。 CA本身也会尝试缓存结果,但如果您自己控制缓存,则会获得更好的结果。
  • 小心透明。绘制不透明图层总是比绘制不透明图层更快。因此,避免在不需要的地方使用透明度,因为系统不会扫描所有内容的透明度(或缺少透明度)。如果NSView不包含其父级所在的区域,请创建自定义子类并覆盖isOpaque以返回YES。同样适用于UIView和图层,父母和兄弟姐妹都不会透过这些图层,但只需将opaque属性设置为YES即可。

如果这些都没有帮助,那么你正在将CA推向极限,你可能需要用其他东西替换它。