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?
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.
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.
Related
This is not really a question about addressing a specific problem but more a request to be pointed in the right direction.
I am making an app where I am loading several images (saved as JPGs) onto a screen at the same time and this has to be the way it is, I can't unload any of them because they are all being shown at once.
I tried loading 30 images at about 800px*600px resolution naively thinking that it only loads the 'compressed' size of the pictures into memory (this being about 200 KB) - so a total of 6 MB? Of course, several memory warnings later, I realised how stupid I was. So i now re-size them to about 400px*300px each and my iPhone 4S just about copes with the memory requirements.
I originally used a UiView where in the drawRect I drew a custom drawn image but changing over to using a UIImageView improved the situation dramatically. The app is so much faster and responsive.
I also found that switching off rasterization in my layer made a huge difference in performance.
These sorts of 'discoveries' have taken me hours to work out and what I was wondering was if there are particular design patterns or resources that I can use to load my images to the screen as efficiently as possible; mainly regarding using as little memory as possible - are there are good rules of thumb? Why did using UIImageView vs UiView w/ image make such a difference?
Would be very grateful if someone could help.
Thanks.
Implementing -drawRect: causes the system to allocate a bitmap image of the same size as your view (after all, you need a buffer to draw in to). If all you're doing is drawing an image you've already loaded, then in one fell swoop you've doubled the memory usage for that image (because you have the copy you loaded, and the second copy you just drew).
Similarly, rasterizing layers requires allocating bitmap images the same size as the layer, so it has a buffer to rasterize into. So turning that on sucks up memory as well (proportional to the size of the layer).
The basic rule of thumb is, don't do extra work. Using -drawRect: to draw an image is extra work. Rasterizing a layer is extra work (though, depending on what the layer is, this may be a one-time performance cost (and a constant memory cost) in order to save on performance later, e.g. if it's a CAShapeLayer or if it's drawing shadows). Keeping large images in memory that you always scale down before rendering to screen is extra work (just scale it down once when you load the image, and keep the scaled copy around).
Another thing to keep in mind is, if your goal is to draw images, you should try to use UIImageView if you possibly can. It's generally the fastest and cheapest way to get an image to the screen, and it's reasonably flexible.
The design pattern is basically, use UIImageView. Apple has spent a lot of time making it fast, and Apple is allowed to use private APIs that you aren't.
That said, if you want to do it yourself, you should try using one CALayer per image. You just load an image and set it as the content property of a CALayer. The CALayer can cache its contents in GPU memory, and may do other optimizations that you can't do with public APIs.
You can learn a lot about making your UI fast by watching Apple's development videos. They include a lot of tips and "inside info" that are either not in the written documentation, or hard to find/easy to overlook in the docs. The development videos are here: http://developer.apple.com/videos/. Some good ones relevant to your question:
iOS - "Understanding iOS View Compositing"
WWDC 2011 - "Understanding UIKit Rendering"
WWDC 2011 - "Practical Drawing for iOS Developers"
WWDC 2011 - "Core Animation Essentials"
WWDC 2010 - "Core Animation in Practice"
I released the first beta version of my iPhone app on TestFlightApp today. Everything is going really well until I notice that the responsiveness of the application is pretty cruddy. Certainly doesn't have a "nice" native feel that I'm going for.
I've been particularly fastidious concerning my memory allocation/deallocation, so I don't think this is the issue. Basically, I don't know where to turn to next in order to improve the performance of my app.
Here's where I think some of my slowdown can be attributed to:
Using UIAppearance to customize the looks of most (if not all) UI elements. I use a brand new font, lots of CAGradientLayers, and lots of edits to CALayer in order to draw nice Shadows.
Grouped UITableViewCells that display pictures of a map and itemized lists.
UITableViewCells whose layouts are updated every time I call layoutSubviews.
UITableViewCells with customized heights. For each call of heightForRowAtIndexPath, I need to reconstruct and re-layout the view, returning the exact height each time.
Because I programmatically created views, controllers with longer viewDidLoad calls tend to load slower. What code can I offset in the init call?
Does anyone have any hints or tips for dealing with these problems? Or perhaps people have stories about how they dealt with a slowdown in performance when they released their first app?
My answer won't address all of your points, but here are a couple:
1) Make sure you are using Shadow Paths. Paths are much, much more performant.
2) Are you using transparency or corner rounding? If so, try and reduce transparency as much as possible and do not round corners using CALayer cornerRadius. Instead, use a clipping mask in the drawRect of the view that needs to be rounded.
4) Perhaps you can cache the height in an array and not have to repeat the calculation, each and every time. This may/may not scale well depending on the potential number of items, but may be acceptable depending on the use case.
5) Are there views you can reuse? For example, when I have a custom selection view on a UITableViewCell, I only create a single instance held by the controller and reference it in all the cells.
Did you run Instruments (or other profiling methodology) to determine where you app is spending most of it's time. It would be a good idea to do this before optimizing the wrong thing.
I know this is kind of a vague question, but does anyone know how to get rid of choppy scrolling on a tableview?
Thanks
I suggest reading up on the subject but a few main things are:
Don't do heavy calculations in your GetCell function (it is calculated several times per second as each row appears.
Make sure as many elements in the cell are "opaque" as possible. Read about red/green transparency checking with instruments / simulator. Transparent areas require compositing which causes a performance hit with the hardware (especially on iPhone/3G).
Make sure you are using a constant cell identifier so dequeueReusableCellWithIdentifier isn't creating a new cell every time one is needed.
Have you tried Apple's suggestions for Cells and Table-View Performance yet?
You might also want to look at the TableViewSuite sample code for different techniques for creating fast scrolling table views.
Try Atebit's fast scrolling table cells:
http://news.atebits.com/post/197580827/fast-scrolling-in-tweetie-with-uitableview
The idea is that instead of a deep hierarchy of subviews you draw everything flat to the content view so that rendering doesn't have to composite lots of transparency.
Another possible problem may be background web calls - if you are using asynchronous calls to servers, try moving them into background threads. That can really improve UI performance.
I'm seeing my app being killed by iOS with an out of memory message, however, while tracing the progress of the app in the Allocations Instrument, I see lots of mallocs that seem to be occurring outside of the code I've written.
I'm not seeing any leaks being caught, so I assume these allocations are supposed to be there. Thing is, because I'm not sure about why they have been allocated, I'm not sure what I can do to optimize the app and prevent the OS from jettisoning my app.
Does anyone know why the memory is being allocated, or is there any way for me to find out?
Here are a couple of shots from Instruments showing the mallocs. In the second shot, all of the allocations have the same stack trace.
EDIT
I' displaying a single large image as the UIView background (1024x768), then overlaying a smaller (600px square) UIView with some custom drawing and a third UIView (550px square) over the top of those that contains two 550px square images overlayed.
I'm guessing that this is not appropriate, and there is probably a better way of achieving the composition of views I need for the app to work.
Should this be possible on the iPad?
I think there's not really much information to go on here - if you add a bit more information about what this view in your app is doing you might get some more informed suggestions.
From the screenshot, it would appears large blocks are being allocated to display an image.
Given that I'd hazard a guess that either you're trying to display some very large images, or you UIView is large, or you have more UIViews in memory that you need to display the current screen.
I guess the easiest way to track down exactly where they're coming from would be to disable the part of the application you suspect then run again and see if the allocations still occur.
EDIT
Are all the images the same size as you're displaying them? (ie. are you trying to display a 5M photo as the 1024x768 background?) If not you probably need to scale them down to the size you are display them, or at least closer.
If you're not needing transparency, make sure to make all the views opaque.
I figured out the source of the problem - I was using
[UIImage imageNamed:#'Someimage']
to load in my images. This, as I'm sure many people are aware, caches the image data. I had enough images of sufficient size to cause my app to be jettisoned.
The problem was apparent not because of the size of the image but because of both the size and number of images I was using. The lesson here is be careful with [UIImage imageNamed:].
Thanks for all of the help, chaps!
Mallocs can occur inside of other API's that your app calls (such as loading images, views, playing long sounds, etc.) You can try changing the size of your images, views, sounds and other objects by various amounts as a test, and see if the size of the malloc'd memory changes track one of the changes that you've made.
I've got an app I'm working on where we handle a LOT of images at once in a scrollview. (Here's how it looks, each blue block being in image on a scrollview expanding to the right: http://i.stack.imgur.com/o7lFx.png) So to be able to handle the large strain doing this puts on memory. So I've implemented a bunch of techniques such as reusing imageviews etc which have all worked quite successfully in keeping my memory usage down. Another thing I do is instead of keeping the actual image in memory (which I of course couldn't do for all of them because that would run out of memory very quickly) I only keep the image's filepath in memory and then read the image when the user scrolls to an area of the scroll view near that image. However, although this is memory efficient, it's causing a LOT of lag in the scrollview because of the fact that it has to constantly read images from the disk. I can't think of a good solution on how to fix this. Basically right now the app draws to the screen only the visible uiimageviews and while the user scrolls the app will look to see if it can dequeue another imageview so it doesn't have to allocate another one and at that point it reads the image into memory, but as I said it's causing the scrolling action to be very slow. Any ideas on a strategy to use to fix this? Does anyone know what the native photos app does to handle this kind of thing? Thanks so much!
I can suggest you a simple solution to balance both the memory and the computer processing. You only keep small images like thumbnails in memory and only keep about 20 of them. One project that I am doing, I keep 20 thumbnail images (100 x 100) recently accessed, which doesn't cost a lot of memory. I believe that it costs about 200 kb all the time but comparing to a general available memory. I think it is good enough.
It also depends on your use case : if user scroll really fast and you don't know when will they go. You can have even smaller images than the thumnail and when you show it on the UIImageView, you resize it to fit. When user stops scrolling for a while. You can start loading bigger images and then you have a nicer images. User may not even notice about the process
I don't think there is a solution that can be fast and using as less memory as possible. Because we have memory, maybe not big but have enough if we use it smartly.
Slow scrolling performance might mean that you're blocking the main thread while loading images. In that case, the scrolling animation won't continue until the images are loaded, which would indeed cause pretty choppy scrolling performance.
It would be better to lazily load requested images in the background, while the main thread continues to handle the scrolling animation. A library that provides this functionality (among other things) is the 'three20' library. See the Tidbits document, and scroll down to the bottom where the 'TTImageView' class is described.
I had a similar issue with a PDF viewer, The recommended way to do this is to have as low a res image as you can get away with and if you are allowing the user to blow the image up/zoom, then have two versions or three versions of that image increasing the res as you go.
Put as much code as you can get away with in the didDecelerate method (like loading in higher res images like vodkhang talks about), rather than processing loads in didScroll. Recycle Views out of scope as you have said. and beware of autoreleased Context based Image Creation functions.
Load images in on background threads intelligently (based on the scrollView Offset position and zoom level), and think about using CALayer/Tiled Layer drawing for larger images.
Three20 (an open source iOs lib) has a great Photo Viewer that can be subclassed, it has thumbnail navigation, large image paging, caching and gestures right out of the box.