Saurabh 😎
WWDC 2010: Advanced Performance Optimization on iPhone OS, Part 2
Focus of talk is working with data effectively (both in-memory and serialized)
Memory
iPhone has virtual memory, but no swap file
Each page can be in one state:
Nonresident
Resident and clean
Resident and dirty
Nonresident page access causes page fault
Resident = page is in physical memory
Anonymous memory (e.g. malloc) is always dirty
File-backed memory starts clean, becomes dirty if modified
If a page is clean, then it must be file-backed and unmodified - hence, clean pages can be reused without having to write to disk (since original contents are in a file)
VM Tracker instrument takes snapshot of virtual memory usage at a point in time (so end up with series of samples over time)
Check growth in dirty size of regions:
- Growing malloc region?
Check for leaks
Use Allocations tool to find backtraces - Growing Core Animation region?
Possible view leaks - Growing dirty
__DATA
region? (uncommon)
Copy on write faults
Global variables that are modified
See also: WWDC 2010 Advanced Memory Analysis with Instruments
Low memory notifications - unique to iOS!
Sent when too much total dirty memory in system
First threshold: apps receive notification
Second threshold: background apps exit, foreground app warned
Final threshold: foreground app killed
applicationDidReceiveMemoryWarning
in app delegate
For multi-tasking, tradeoff between releasing resources in applicationDidEnterBackground:
and also keeping enough resources to enable fast loading if user switches back to it
Choose right method for images:
[UIImage imageNamed:]
for images used in UI elements (e.g. custom controls, cells)
[UIImage imageWithContentsOfFile:]
for all other images
ImageIO - refer to Creating a Thumbnail Image in the Image I/O Programming Guide to create low-memory thumbnails from large images
Overall memory goal: reduce dirty memory usage
Foundation
Unique performance of NSMutableArray
:
Insertion/deletion at beginning of array is amortized O(1) (usually is O(n) in textbooks), so can be used as queue
Becomes a tree at ~250,000 elements, so access to individual elements becomes O(log N)
-hash
good enough implementation: XOR -hash
of each instance variable object
Property Lists:
- Use binary plists over XML plists
- Plist in app bundle automatically converted to binary format at build time
Don't use NSCoding
with large object graphs
File System
Use System Usage instrument - look for unexpected I/O and backtrace that caused it
Avoid [NSData dataWithContentsOfFile:]
for large files since it reads entire file into buffer
Alternative 1: demand page data with [NSData dataWithContentsOfMappedFile:]
Alternative 2: incrementally read/seek with [NSFileHandle readDataOfLength:]
Avoid repeatedly open()
/stat()
'ing a file since it incurs an extra iOS-specific check to see if your app has access to that path
Databases
Useful for incremental reading and datasets larger than memory
Core Data features: automatic schema management, iPhone specific enhancements
SQLite tips:
- Cache prepared statements, but only if you plan to actually re-use the query (otherwise waste of memory)
- Order of tables in JOIN can affect query plan
- Watch out for transient tables
EXPLAIN will show an OpenEphemeral instruction
Causes: sorting without an index, subselects
Will make first sqlite3_step take a long time - Don't store large blobs in database, or will crowd out page cache (since entire blob will be loaded, occupying many pages)
Instead, store pointers to files on filesystem in database - Surround batch insertions and updates with transactions so autocommit won't create transaction for each statement (because of logging, 2x write traffic overhead for each transaction)
Scaling Tips
Avoid bringing in entire data set for view loading time
Test and profile with different data set sizes
For loading sections quickly, Contacts app uses a separate table for pre-computed section counts, maintained by triggers
For loading table view cells quickly:
LIMIT/OFFSET is not particular efficient in SQLite, but works if iterating over small index
Can also use "scrolling cursor" method