Performance Optimization
Performance Optimization
Section titled “Performance Optimization”Optimizing Core Data performance requires understanding where bottlenecks occur and applying targeted solutions.
Profiling with Instruments
Section titled “Profiling with Instruments”Time Profiler
Section titled “Time Profiler”- In Xcode: Product → Profile
- Select Time Profiler
- Record while using app
- Find heaviest stack traces
Look for:
- Excessive faulting
- Slow fetch requests
- Save operations taking too long
Allocations Instrument
Section titled “Allocations Instrument”- Product → Profile
- Select Allocations
- Monitor memory growth
- Identify retained objects
Look for:
- Unbounded memory growth
- Objects not being released
- Large allocations
SQL Debug Logging
Section titled “SQL Debug Logging”Enable SQL logging:
-com.apple.CoreData.SQLDebug 1Output:
CoreData: sql: SELECT Z_PK, ZNAME FROM ZARTICLE WHERE ZVIEWS > ? LIMIT 20CoreData: annotation: sql execution time: 0.0023sAnalyze:
- Query complexity
- Execution time
- Number of queries (N+1 problem)
Common Performance Issues
Section titled “Common Performance Issues”1. N+1 Query Problem
Section titled “1. N+1 Query Problem”Problem:
// Fetches articleslet articles = try context.fetch(Article.fetchRequest())
// Each access fires a fault (N queries)for article in articles { print(article.category?.name) // Fault!}Solution:
let fetchRequest = Article.fetchRequest()fetchRequest.relationshipKeyPathsForPrefetching = ["category"]let articles = try context.fetch(fetchRequest)
// No faults firedfor article in articles { print(article.category?.name) // Already loaded}2. Fetching Too Much Data
Section titled “2. Fetching Too Much Data”Problem:
// Fetches all properties of all objectslet articles = try context.fetch(Article.fetchRequest())let count = articles.countSolution:
// Only counts, doesn't fetch objectslet count = try context.count(for: Article.fetchRequest())3. Not Using Batch Sizes
Section titled “3. Not Using Batch Sizes”Problem:
// Loads 10,000 objects into memorylet fetchRequest = Article.fetchRequest()let articles = try context.fetch(fetchRequest)Solution:
fetchRequest.fetchBatchSize = 20// Only loads 20 at a time4. Fetching Unnecessary Properties
Section titled “4. Fetching Unnecessary Properties”Problem:
// Fetches all propertieslet fetchRequest = Article.fetchRequest()Solution:
fetchRequest.propertiesToFetch = ["name", "creationDate"]// Only fetches needed properties5. Saving Too Frequently
Section titled “5. Saving Too Frequently”Problem:
for item in items { item.processed = true try? context.save() // Very slow!}Solution:
for item in items { item.processed = true}try? context.save() // Save once6. Not Resetting Context
Section titled “6. Not Resetting Context”Problem:
// Context accumulates objectsfor i in 0..<10000 { let article = Article(context: context) // Memory grows unbounded}Solution:
for i in 0..<10000 { let article = Article(context: context)
if i % 100 == 0 { try? context.save() context.reset() // Clear memory }}Memory Management
Section titled “Memory Management”Context Reset
Section titled “Context Reset”context.reset()When to use:
- After processing large batches
- When context accumulates many objects
- To free memory
Caution: Invalidates all fetched objects from this context.
Refresh Objects
Section titled “Refresh Objects”context.refresh(article, mergeChanges: false)When to use:
- Discard in-memory changes
- Free memory for specific object
- Reload from database
Turn Objects into Faults
Section titled “Turn Objects into Faults”context.refreshAllObjects()When to use:
- Free memory across all objects
- After large operations
- When memory is constrained
Fetch Request Optimization
Section titled “Fetch Request Optimization”Checklist
Section titled “Checklist”let fetchRequest = Article.fetchRequest()
// ✅ Set batch sizefetchRequest.fetchBatchSize = 20
// ✅ Limit propertiesfetchRequest.propertiesToFetch = ["name", "views"]
// ✅ Prefetch relationshipsfetchRequest.relationshipKeyPathsForPrefetching = ["category"]
// ✅ Use predicate to filterfetchRequest.predicate = NSPredicate(format: "views > %d", 100)
// ✅ Set fetch limit if applicablefetchRequest.fetchLimit = 10
// ✅ Specify sort descriptorsfetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]Batch Operations
Section titled “Batch Operations”For large-scale operations, use batch requests:
// Instead of:for article in articles { article.isRead = true}try context.save()
// Use:let batchUpdate = NSBatchUpdateRequest(entityName: "Article")batchUpdate.propertiesToUpdate = ["isRead": true]try context.execute(batchUpdate)Benefits:
- 10-20x faster
- Lower memory usage
- SQL-level operations
Data Generators for Testing
Section titled “Data Generators for Testing”Create reproducible test datasets:
class DataGenerator { func generate(count: Int, in context: NSManagedObjectContext) { for i in 0..<count { let article = Article(context: context) article.name = "Article \(i)"
if i % 100 == 0 { try? context.save() context.reset() } } try? context.save() }}
// Usagelet generator = DataGenerator()generator.generate(count: 10000, in: backgroundContext)Profiling Checklist
Section titled “Profiling Checklist”- Enable SQL debug - See actual queries
- Profile with Time Profiler - Find slow operations
- Profile with Allocations - Find memory issues
- Test with realistic data - Small datasets hide problems
- Monitor on device - Simulator performance differs
- Test on older devices - Performance varies
Quick Wins
Section titled “Quick Wins”- Use
count(for:)instead of fetching - 100x faster - Set
fetchBatchSize- Reduces memory - Prefetch relationships - Eliminates N+1 queries
- Use
propertiesToFetch- Reduces data transfer - Reset context periodically - Frees memory
- Use batch operations - 10-20x faster for bulk changes
- Save conditionally - Check
hasPersistentChanges - Use background contexts - Keep UI responsive
Summary
Section titled “Summary”- Profile first - Measure before optimizing
- Use Instruments - Time Profiler and Allocations
- Enable SQL debug - Understand query behavior
- Optimize fetch requests - Batch size, properties, prefetching
- Use batch operations - For large-scale changes
- Reset contexts - Free memory periodically
- Test with real data - Small datasets hide issues
- Monitor on devices - Real-world performance matters