In looking for some performance improvements to make in my app Portfolio, profiling in Instruments pointed at the parts responsible for image reading and writing. For this I started by building a test app to benchmark a few different approaches.
iOS provides a variety of methods for reading and writing images. When used in moderation there’s usually little need to be concerned about which one(s) you choose, but when used heavily performance becomes an important consideration.
This benchmark shows the average of the time required to save a large-ish image (4096×1612) for three trials of 100 iterations. As you can see, saving the image as a moderately compressed JPEG is is over 800% faster than saving a PNG (saving a single PNG of that size averages 1.41 seconds), and saving direct BGRA data is still 200% faster.
An interesting observation I also made is that using the ImageIO framework to save a JPEG is effectively the same as using UIImageJPEGRepresentation but doing the same approach with PNG is slower than using UIImagePNGRepresentation.
The benchmarks I ran for image reading performance showed it was a significantly less expensive operation than writing, even in the worst case. These are also using the same size image and are the average for three trials of 100 iterations.
For most developers the first approach is likely to be the most common, and, for smaller image files, it’s probably sufficient. As shown by the third approach though, if you can afford the time and storage space to pre-cache the raw BGRA pixel data of an image, its speed increase is substantial. It also has the benefit of being already in a format Core Animation likes and won’t cause a background memory copy.
The middle option is one currently used by Portfolio to minimize memory usage – it loads only the size it needs rather than bringing the entire file into memory. Over its five year life more crashes have resulted from iOS suddenly deciding it was using too much memory than to any actual bug, which made the loading speed tradeoff worthwhile.
When you’re using small images there will likely be much more worthwhile performance gains to be had by optimizing other areas of your app. Large images are where image reading and writing optimizations really start to be important. Since writing an image can often be deferred to a background thread without any significant impact to the responsiveness of your app, writing out the BGRA data directly from a bitmap CGContextRef is usually worth the increase in time over saving a JPEG. In cases where you need lossless saving, it’s definitely an improvement over saving a PNG.
Reading an image from disk is a point where speed matters immensely because it’s often triggered by an immediate need to display the image. The longer it takes to load, the lower your framerate will be, and your interface will feel sluggish. For this reason it’s almost essential for you to pre-cache the image size(s) you’ll need to ensure fast reading later.