3

I have a single container view inside a UIScrollView, and that container view has several CAShapeLayer based subviews (about 200). Each CAShapeLayer contains a very simple CGPath (a filled polygon of about 10 points). You can see it as a sort of map.
The container view itself is big (about 1000x2500 points) but I have implemented zooming using the transform property (I'm not using UIScrollView implementation), so at small scales it's entirely visible.

At high scales (only a part of the container view is visible on screen) this works great and scrolls smoothly, even on old hardware.
However, at small scales (when most of the container view is visible), this results in very bad scrolling performance on old hardware (40 fps on an iPhone 4S). And if I go over 200 subviews (which is something I would like to do), this is much worse (down to 15fps) even on newer hardware.

I have done some profiling but I can't find the bottleneck. During scrolling I make the following measurements (on average) :

Activity monitor instrument :
    CPU : 8%
GPU driver instrument :
    Device utilization : 40%
    Renderer utilization : 35%
    Tiler utilisation : 8%
    FPS : 40

Here is what I have tried :

  • Setting shouldRasterize with the proper rasterizationScale on all CAShapeLayer. It makes things worse.

  • Setting shouldRasterize with the proper rasterizationScale on the layer of the container view. This improves scrolling for very small scales (when the entire container is visible inside the scrollView), but makes it much worse for bigger scales. Activating rasterization only for for small scales leaves a gap (between 0.5 an 2.0 approximately) where both options result in lost frames.

  • Using layers only instead of views. Doesn't improve anything.

  • Using CATiledLayer for the container view layer. Doesn't improve anything.

Any ideas ? If there was a limitation of the hardware shouldn't I see a 100% somewhere ? What else should I be profiling ?

At this point the main thing that I'm asking is help on how to profile my app and understand what is causing lost frames. Then maybe, depending on what is happening and what is doable, I will try to improve things. I'm not asking for every single possible tweak in the book that could maybe improve things a little if i'm lucky.

EDIT

Just found out that although my app is running at 8% CPU, backboardd runs at 100% CPU during scrolling. So the bottleneck is in the Core Animation render server !
Now I just have to figure out why. Maybe there are just too many layers... Anyone knows what is the maximum number of layers the render server can process every 16ms ? I can't find any official documentation about that.

EDIT 2

So after some more profiling it looks like it has nothing to do with the number of layers. If I combine all the shapes in a small number of layers (around 10) by combining some of the paths (and as a result using more complex paths), the result is approximately the same. backboardd still runs at 100% CPU during scrolling.
So I guess that it's the paths processing/drawing/something that takes time, regardless of wether or not they are split in smaller/simpler paths. Maybe it's simply bound to the number of points. I'm starting to think that there is nothing that I can do...

deadbeef
  • 5,409
  • 2
  • 17
  • 47
  • have you tried putting the layers in a single parent layer and setting shouldRasterize on that? – nielsbot Mar 06 '16 at 19:19
  • I have. And although it works very well at very small scales (when the entire view is visible inside the scrollView), scroll performance is terrible as soon as the view gets bigger. Activating rasterization only for for small scales still leaves a gap (between 0.5 an 2.0 approximately) where both options result in lost frames (at high scales, non rasterized view scrolls at 60 fps). – deadbeef Mar 06 '16 at 19:57
  • Are those subviews changes after they initialised? If not, what you can do is just taking a screen shot of the content view and displaying it instead of all those shapes. This practice is actually shown and suggested in apple's offical WWDC videos. – Yunus Eren Güzel Mar 06 '16 at 20:48
  • That's an idea, but isn't it what rasterization basically does ? – deadbeef Mar 06 '16 at 20:51
  • And by the way I also have zooming on this scrollView. I'm only talking about scrolling here because I was hoping that fixing scrolling would improve zooming as well, but any kind of screenshot based solution (including rasterization) needs to be disabled during zooming which isn't optimal for me. I will only fall back to this as a last resort. What I would like too understand for now is where the performance bottleneck is ? – deadbeef Mar 06 '16 at 21:01
  • is this sort of like a map view? you can try using a CATiledLayer and render tiles? Or maybe you have to use GL or SceneKit... Can you upload your code somewhere? – nielsbot Mar 06 '16 at 22:28
  • Also ,maybe use just layers and not views for your shapes? – nielsbot Mar 06 '16 at 22:29
  • It it a sort of map view yes. I have tried using `CATiledLayer` as well but it doesn't work either. Actually I don't think it works if you don't implement custom drawing which in my case would probably be worse than using a bunch of `CAShapeLayer`. I use a lot of UIKit and Core Animation features on top of that, so moving to a different framework is not an option for me unfortunately, and I have never used SceneKit... I have tried a layer only hierarchy where only the container view is a UIView and everything else is just sublayers, but it doesn't improve anything either. – deadbeef Mar 07 '16 at 00:44
  • My code is wrapped in a lot of other logic, it would be cumbersome to read and understand. But really at this point I need help on how to profile it and understand where is the performance bottleneck, and then maybe consider either a solution or an alternative. – deadbeef Mar 07 '16 at 00:44
  • Just to rule out the obvious, are you testing on an actual device? The simulator isn't a reliable source when validating performance. – MDB983 Mar 16 '16 at 14:45
  • I'm doing most of my testing on an iPhone 5. Ideally I would like this to work on iPhones 4, but for now iPhone 5 will be enough. – deadbeef Mar 16 '16 at 14:48
  • As you note, profiling is where you must start. Otherwise you're shooting in the dark. So launch Instruments, and start with the Time Profiler and see where you're using your time. From there you can move to more complicated things like the Core Animation instrument, but usually the Time Profiler will answer the question. Obviously you should start with the Instruments User Guide to learn your way around the tool: https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/index.html – Rob Napier Mar 16 '16 at 16:07
  • @rob-napier I did already try a lot of things. I'm past the very simple things like profiling the stack trace and cpu usage (see the comments of Terence's answer). – deadbeef Mar 16 '16 at 18:53
  • I don't see in there where you explain which lines of code actually have the highest CPU utilization, beyond "the handling of the gesture recognizer." So, which part of the gesture recognizer? In your callback, or inside Cocoa? Your noted mitigations don't seem related to the gesture recognizer. It is almost impossible for others to profile your code based on "it scrolls slowly." There are dozens of things it could be. Instruments is the tool to determine which of those dozens of things are actually involved. – Rob Napier Mar 16 '16 at 18:58
  • I'm almost 100% sure that there is no CPU limitation here, at least not in an obvious time profiler instrument measurable way. I have a scrollView, one container view inside it and I scroll. All gesture recognizer management is handled by UIKit. The CPU usage percentage is very low anyway so I think we can rule this out. – deadbeef Mar 16 '16 at 19:02
  • I understand that you can't guess what's going on. What I was asking was more "what should I search for with which instrument". But having spent a lot of time in instrument by now, I find that there is not that many tools. The gpu driver gives me a somewhat high renderer utilization but nothing more. I have no idea why and mostly no idea if i should be worried by that at all. Is 40% enough to cause lost frames ? – deadbeef Mar 16 '16 at 19:06
  • @rob-napier You were right, although not in an obvious way. See my edit, the problem **is** CPU related, but the bottleneck comes from the render server, not my app. Any idea how can profile that ? – deadbeef Mar 17 '16 at 13:18

0 Answers0