我试图通过我的应用程序使用Core Data来挤压每一次性能提升。使用大型数据集进行测试,通过重构对象图并修改提取中使用的各种谓词,我获得了一些显着的收益。在性能方面,我唯一不满意的是在大型删除操作后保存上下文。结果,我最终使用父子上下文结构实现了线程。但是,我对性能提升感到不知所措。
对象图:我只显示关系,因为我知道这会使删除变得昂贵。
国家:
CookingStyle:
配方:
为了争论,假设每种烹饪方式对于一个国家来说都是独一无二的。因此,将CookingStyle
country
关系配置为一对多关系不会重复数据删除。每种食谱也是如此,即每种食谱对于烹饪风格是独特的。
效果测试:
数据集: 18个国家/地区对象,每个Country
有10个CookingStyle
个对象,每个CookingStyle
有35个Recipe
个对象(总计= 18个) Country
个对象,180个CookingStyle
个对象,6,300个Recipe
个对象)
我使用Time Profiler测量运行iOS 7.1的5G iPod Touch的性能。
我使用父子NSManagedObjectContext
个实例来实现线程。
发生删除的表视图控制器使用NSFetchedResultsController
。
结果:删除一个Country
对象后保存父子上下文仍需要大约1000毫秒。在实现线程之前,删除Country
对象大约需要1200-1300毫秒。
当我深入了解Time Profiler结果时,从子上下文保存到父上下文会阻塞主线程。后者保存没有击中磁盘,所以我认为它会更快。
问题:
在测试用例下我的期望是否太高,或者在删除Country
个对象时我还应该做些什么来提高性能?
代码:
我如何创建父子情境:
_model = [NSManagedObjectModel mergedModelFromBundles:nil];
_coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_model];
_parentContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_parentContext performBlockAndWait:^{
[_parentContext setPersistentStoreCoordinator:_coordinator];
[_parentContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
}];
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_context setParentContext:_parentContext];
[_context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
我如何保存上下文(来源:" Core Data",2nd Ed。,Marcus Zarra):
- (void)saveContext:(BOOL)wait
{
if (!self.context) return;
if ([self.context hasChanges]) {
[self.context performBlockAndWait:^{
NSError *error;
if ([self.context save:&error]) {
QuietLog(@"\nchild context SAVED changes to parent context");
} else {
QuietLog(@"\nchild context FAILED to save: %@", error);
[self showValidationError:error];
}
}];
}
void (^saveParentContext)(void) = ^{
NSError *error= nil;
if ([self.parentContext save:&error]) {
QuietLog(@"\nparent context SAVED changes to persistent store");
} else {
QuietLog(@"\nparent context FAILED to save: %@", error);
[self showValidationError:error];
}
};
if ([self.parentContext hasChanges]) {
if (wait) {
[self.parentContext performBlockAndWait:saveParentContext];
} else {
[self.parentContext performBlock:saveParentContext];
}
}
}
我如何删除并保存:
- (void)deleteButtonTapped:(UIBarButtonItem *)sender
{
AEBCoreDataHelper *coreDataHelper = [AEBCoreDataHelper sharedCoreDataHelper];
Country *country = ...
[coreDataHelper.context deleteObject:country];
// NSFetchedResultsController's delegate will delete the row for me
[coreDataHelper saveContext:NO]; // blocks main thread for too long
}
更新
我认为在保存父子环境时,UI没有响应。不是。相反,任何涉及Core Data的内容都没有响应,直到父上下文(专用队列)完成其保存到磁盘。 UI似乎没有响应,因为我试图滚动表视图,这需要从获取的结果控制器中获得另一批数据。如果我在删除对象之前滚动整个表视图,则在删除后滚动很快就会响应。使用Core Data进行多线程感觉就像恶作剧一样。
答案 0 :(得分:3)
如果父级有更改但是在没有更改时没有阻止,则阻止保存父级。这不会让你获得任何表现。
如果您没有更改,则无需保存,实际上是无选择。
如果您有更改,则希望它们以异步方式运行,以便主线程可以返回为用户提供服务。阻止主线程没有任何价值,因为主要上下文已经知道更改,它所做的任何事情都不会影响保存。
最后,使用私有上下文不会“更快”。它会让用户感觉更快,因为他们可以更快地获得控制权。线程与性能没有直接关系。卸载保存的想法是,用户可以返回使用应用程序而不是等待保存。保存很容易,但用户并不在意。
如果您在滚动条上阻止了UI,则表明您的保存时间过长。
点击NSPersistentStoreCoordinator
的 任何活动会锁定它。长时间获取或长时间保存无关紧要。这是生活中的事实。有办法避免这个规则,但只是抛出问题的线程不会解决它。您需要处理锁定NSPersistentStoreCoordinator
的基本问题。
首先,我会考虑将保存拆分为较小的单位。使保存更小允许NSPersistentStoreCoordinator
编织保存和提取,以便人类不会感知到保存。
如果您不能这样做,请考虑针对单独的NSPersistentStoreCoordinator
执行删除操作。这假设您使用的是SQLite。然后,您可以通过通知将更改反馈给主NSManagedObjectContext
。请注意,这些通知的消耗将对主NSManagedObjectContext
产生性能影响,因此会影响用户体验。
您还可以在UITableView
中探索对象的预发生故障,这样就无需返回磁盘。但是,你正在为内存交易速度/性能,而在iOS上,当你这样做时,你往往会失败。