views:

348

answers:

2

I am trying to improve the performance of scrolling in our app. I have followed all of the generally accepted advice (draw it yourself with CG, the cell is opaque, no subviews, etc.) but it still stutters sometimes when we have background CPU and network activity.

One solution stated here:

http://www.fieryrobot.com/blog/2008/10/08/more-glassy-scrolling-with-uitableview/

is to cache bitmap snapshots of the cells, which we tried. But the bitmaps were fuzzy and took up a ton of memory (~a few hundred kb each).

One suggestion in the comments of that link is to cache the cells using CGLayer or CALayer(?) because they go to the graphic card's memory. So a few questions,

1) Has any tried this? Sample code?

2) How much memory does the iphone/ipod touch graphics card have? Does this make any sense?

3) Any other suggestions for speeding things up?


More information

I used the CPU sampler (on the phone) and systematically eliminated things from the cell to figure out the problem. A few things:

1) It isn't the setup of the cell. If I remove just the drawing calls (drawinrect etc), but leave the setup, it is glassy.

2) It isn't the drawing of the smallest image (25x25 png), if I put that in it is fine.

3) If I add the second or third image (the first is a big background 320x1004kB, the other is a button image 61x35 4kB) it stutters. I am grabbing both UIImages in a class method, so it is cached.

4) The text is also a problem. It looks like by drawRect spends 75% of its time in the three NSString drawInRect methods I am using. Like:

[mytext drawInRect:drawrect withFont:myFont lineBreakMode:UILineBreakModeTailTruncation];

Those calls seem to go through webcore, perhaps that causes some of the stutters? I need to be able to format two lines of text, and one small paragraph of text. I need the ability to truncate the text with ellipses. Can I perform these out of the critical path and cache them? May I can do that part with a layer?

A: 

Before you go too deep into the optimisation, are you using the cell reuse mechanism ([tableView dequeueReusableCellWithIdentifier:]) for creating UITableViewCells in your delegate's tableView:cellForRowAtIndexPath: ?

Also, have you run the code in the Simulator using Instruments' Activity Monitor? Set the interval to 1 ms (I found the default - 10 ms - too big for this) and see where most of your time is spent when doing the scrolling.

Adam Woś
I added some more information to the original post to address this question. Still there has to be some way to cache the cells in a reasonable amount of memory?
Mark Corner
Actually, the solutions you linked to is a bit extreme, and most of the iPhone developers use the basic cell reuse mechanisms, leaving the drawing to the device itself. If you could post your (stripped) `tableView:cellForRowAtIndexPath:`, it would be helpful - we should really start from there before we go to drawing performance.
Adam Woś
I am using the reuse method, the problem doesn't appear to be there as I can do all of the reuse and setup the cell, but if I don't draw anything the performance is fine. I have actually gone so far as to cache the cells myself, so I don't have to even set up the data for them, but that isn't the bottleneck.
Mark Corner
Well, as *Brad* also said, on iPhones 3G, "it still stutters sometimes when we have background CPU and network activity." -- this just happens and I'm afraid you'll have to live with it. Especially since you say you're downloading a lot ("It is quite frustrating because we want to download stuff as fast as possible, but our users complain about the scrolling") - this IS CPU-consuming.
Adam Woś
Yes, I would have hoped that the iPhone would do a better job with thread priorities. But I would like to find a workaround, like caching, or prerendering, the cells. As I scroll up and down it is performing the drawrect repeatedly. I can't imagine I can't get this rendered statically into a reasonable amount of memory. I could easily make the whole thing in html and it would scroll just fine, so there has to be a way to cache it :)
Mark Corner
Is it really that bad with iPhone's built-in drawing? In one of my apps I am using cells with transparency, with multiple nested (and transparent!)subviews, and I have no drawing code written by myself - and the iPhone 3G is doing a pretty good job. That is to say, it stutters sometimes when going to the next row, but almost nobody has complained, and people who have noticed are basically used to it.
Adam Woś
Also, one thing just occurred to me. Are you correctly handling the `setNeedsDisplay` etc.? It seems to me the cell should be drawn only once, and the drawn cell should be cached by table view's internal mechanisms?
Adam Woś
+1  A: 

CGLayers are not cached on the GPU, they are used during the process of drawing Core Graphics elements to a context. CALayers do have their visual contents cached on the GPU. This can "hide" some of your memory usage, but you're still going to run into memory problems if you hold on to a lot of CALayers.

Honestly, if you've followed the table view best practices, as described by Loren Brichter and others, of drawing all of your content via Core Graphics in one layer, making your cell opaque, and obeying the cell reuse mechanism of the table view, there isn't much more you can do. Overloading the CPU on the iPhone will cause stuttering of your scrolling, no matter how optimized you can make it. The inertial scrolling animation does require some CPU power to run.

One last thing to make sure of is that the background CPU and network processes you refer to really are running on a background thread. Anything running in the main thread will cause your interaction methods to pause while that task is processing, potentially adding to the choppiness of your scrolling.

Brad Larson
Yep, all of the networking goes through operation queues, and we have used instruments to eliminate any network calls on the main thread. It is quite frustrating because we want to download stuff as fast as possible, but our users complain about the scrolling (of course on the 3Gs it is fine!)
Mark Corner
Another question, if I did want to cache with CALAyer, how might I do that? And would the memory consumption be similar, or better than caching with a bitmap?
Mark Corner
You could do all of the Quartz drawing that you do now for your custom UITableViewCell subclass in a CALayer by either subclassing it and placing the drawing code in -renderInContext: or by placing the code in the delegate's -drawLayer:inContext: method. The CALayer could then be generated as needed, stored in an NSMutableDictionary, and retrieved for each row as it needs to be displayed (with the dictionary key being the row number). To display it, you'd need to add the layer as a sublayer of the UITableViewCell to be used for a row.
Brad Larson