iOS / Core-Animation:性能调优

我有我的应用程序在我的iPad上运行。 但performance非常糟糕 – 我低于15fps。 任何人都可以帮助我优化?

它基本上是一个包含12个button(从UIControl派生)的轮子(派生自UIView)。 控制的形象

当用户旋转它时,button会dynamic扩展和收缩(例如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]; } 

…从最近的触摸input计算轮子的新的旋转。 已经有一个性能问题,我正在寻找一个单独的线程: iOS Core-Animation:CATransaction / Interpolating变换matrix的性能问题

这个程序还检查车轮是否已经完成了另一个1/12的旋转,如果是,则指示所有12个button的大小调整:

 // 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]; } } 

现在对于实际的resize的代码,在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.]; } 

我不知道这个问题是否与多个转换的开销有关,比如排队等比例的操作 – 每个button的大小调整都需要一秒钟的时间才能完成,如果我快速旋转轮子,我可能会每秒钟旋转几圈; 这意味着每个button每秒resize24次。

**创buildbutton的图层**

我猜的最后一块难题是查看button的contentsLayer。 但我已经尝试过了

 contentsLayer.setRasterize = YES; 

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

我不敢相信这是超出其极限的设备。 不过,核心animation工具告诉我,否则; 当我旋转轮子(通过拖动我的手指在圈子),它报告〜15fps。

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

必须有一些我做错了! 但是什么?

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

 - (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

**妥协的UIdevise,以获得可容忍的帧率**

编辑:现在我已经尝试禁用dynamicresize的行为,直到车轮安定到一个新的位置,并设置wheel.setRasterize = YES。 所以它有效地旋转了一个单独的预渲染UIView(这是承认占据大部分的屏幕)在手指下(它愉快地@@ 60fps),直到轮rest,在这一点上,它执行这个laggy调整animation@ <20FPS)。

虽然这给了一个可以忍受的结果,但我似乎很难以这种方式牺牲我的UIdevise。 我确信我一定在做错事。

编辑:我刚刚尝试作为一个实验手动调整button; 即在每个button中放置一个显示链接callback,并dynamic计算给定框架的预期大小,使用CATransaction显式禁用animation,就像我对轮子所做的一样,设置新的变换matrix(从预期的大小生成的变换matrix)。 添加到这个我已经设置button内容层shouldRasterize = YES。 所以它应该简单地缩放每个帧12位图到一个单独的UIView本身旋转。 令人惊讶的是,这是死的缓慢,它甚至使模拟器停下来。 这绝对比自动使用核心animation的animationfunction慢10倍。

我在开发iPad应用程序方面没有任何经验,但我在优化video游戏方面有一些经验。 所以,我不能给出确切的答案,但我想提供一些优化技巧。

不要猜测。 configuration文件。

看来你正在尝试进行更改而不分析代码。 改变一些可疑的代码,并通过你的手指并没有真正的工作。 您应该通过检查每个任务需要多long以及需要多long运行来configuration您的代码。 尝试分解你的任务,并把分析代码来衡量timefrequency 。 如果可以测量每个任务使用多lessmemory或多less其他系统资源,则会更好。 根据证据找出你的瓶颈,而不是你的感觉。

对于您的具体问题,当您resize的工作开始时,您认为程序变得非常慢。但是,您确定吗? 这可能是别的。 我们不知道,直到我们有实际的分析数据。

最小化问题区域并在进行更改之前衡量实际性能。

分析后,你有一个候选人的瓶颈。 如果你仍然可以把原因分成几个小任务,那就去分析它们,直到你不能再分裂为止。 然后,尝试通过反复运行几千次来衡量其精确性能。 这一点很重要,因为您在进行任何更改之前需要知道性能(速度和空间),以便将其与未来的更改进行比较。

对于您的具体问题,如果resize真的是问题,请尝试检查它的执行情况。 执行一次resize需要多长时间? 你需要多长时间调整一下工作来完成工作?

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

现在,你有问题的代码块和当前的性能。 你现在必须改善它。 怎么样? 那么,这真的取决于问题是什么。 你可以searchbetter algorithms ,如果你可以延迟计算,直到你真的需要执行,你可以做lazy fetching ,或者如果你经常做同样的操作,可以通过caching一些数据来进行over-eager evaluation 。 你知道原因越好,你就越容易改善。

对于你的具体问题,这可能只是OS UIfunction的限制。 通常情况下,resizebutton不仅仅是resizebutton本身,但它也invalidates整个区域或其父部件invalidates使每个用户的东西可以正确呈现。 resizebutton可能是昂贵的,如果是这样的话,您可以通过简单地使用基于图像的方法而不是基于OS ui的系统来解决问题。 如果OS API的图像操作不够,可以尝试使用OpenGL。 但是,再次,我们不知道,直到我们实际尝试了他们,并profile这些替代品。 祝你好运! 🙂

尝试没有你的阴影,看看是否提高性能。 我想这会大大改善。 然后我会考虑使用CALayer的阴影path来渲染阴影。 这将大大提高阴影渲染性能。

来自去年WWDC的苹果核心animationvideo有很多关于核心animation性能提升的信息。

顺便说一下,我现在正在为一些更复杂的事情制作animation,而且即使在较老的iPhone 3G上,它也能够很好地工作。 硬件/软件是相当有能力的。

你应该在OpenGL中重新做这个

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

我知道这个问题是旧的,但仍然是最新的。 CoreAnimation (CA)只是OpenGL的一个封装,或者也可能是Metal的封装。 实际上,图层是在矩形上绘制的纹理,animation是使用3D变换表示的。 由于所有这些都是由GPU来处理,所以它应该是超快的,但事实并非如此。 整个CA子系统似乎相当复杂,AppKit / UIKit和3D世界之间的翻译比看起来更难(如果你曾经试图自己写这样一个包装器,你知道它有多难)。 对于程序员来说,CA提供了一个超级简单的界面,但是这个简单的代价是一个价格。 我所有的优化非常慢的CA的尝试都是徒劳的, 你可以加快一点,但是在某些时候你必须重新考虑你的方法:CA可以快速完成工作,或者你需要停止使用CA,或者使用经典的视图绘图(如果CPU可以解决这个问题),或者使用3D API自己实现animation(然后GPU就可以完成),在这种情况下,您可以决定3D世界与其他应用程序的交互方式; 价格是更多的代码写或更复杂的API使用,但结果将说明自己到底。

不过,我想提供一些有关加快CA的通用技巧:

  • 每次“绘制”图层或将内容加载到图层(新图像)时,需要更新支持此图层的纹理的数据。 每个3D程序员都知道:更新纹理非常昂贵 。 避免这一点,不惜一切代价。
  • 不要使用巨大的图层,就好像图层太大而无法直接被GPU作为单个纹理处理一样,它们会被分割成多个纹理,这只会使性能变差。
  • 不要使用太多的图层,因为GPU可以在纹理上花费的内存量往往是有限的。 如果你需要更多的内存超过这个限制,纹理被换出(从GPU内存中移除,为其他纹理腾出空间,稍后在需要绘制时再添加)。 看上面的第一个技巧,这种交换是非常昂贵的
  • 不要重绘那些不需要重画的东西,而是将图片caching到图片中。 如绘制阴影和绘制渐变都是昂贵的 ,通常很less改变。 因此,不要让CA每次绘制它们,只需将它们绘制到图层上,或者将其绘制到图像上,然后将该图像加载到CALayer ,然后将图层放置在需要的位置。 监视什么时候需要更新它们(例如,如果对象的大小已经改变),然后重新绘制它们,一次又一次地caching结果。 CA本身也尝试caching结果,但如果您控制自己的caching,则会获得更好的结果。
  • 小心透明。 绘制一个不透明的层总是比绘制不透明层要快。 因此,避免在不需要的地方使用透明度,因为系统不会扫描您的所有内容以获得透明度(或缺less透明度)。 如果一个NSView包含其父对象的区域,则创build一个自定义的子类并覆盖isOpaque以返回YES 。 对于UIView和其父层和兄弟层都不会发光的图层也是如此,但是在这里只需将opaque属性设置为YES

如果没有一个真正有帮助的话,那么你就是把CA推向极限,而你可能需要用别的东西代替它。