从后台节省中获得小核心数据性能

时间:2014-04-09 00:02:49

标签: ios objective-c multithreading performance core-data

我试图通过我的应用程序使用Core Data来挤压每一次性能提升。使用大型数据集进行测试,通过重构对象图并修改提取中使用的各种谓词,我获得了一些显着的收益。在性能方面,我唯一不满意的是在大型删除操作后保存上下文。结果,我最终使用父子上下文结构实现了线程。但是,我对性能提升感到不知所措。

对象图:我只显示关系,因为我知道这会使删除变得昂贵。

国家:

  • cookingStyles(to-many relationship)

CookingStyle:

  • country(反向关系,一对一)
  • 食谱(对多关系)

配方:

  • 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进行多线程感觉就像恶作剧一样。

1 个答案:

答案 0 :(得分:3)

如果父级有更改但是在没有更改时没有阻止,则阻止保存父级。这不会让你获得任何表现。

如果您没有更改,则无需保存,实际上是无选择。

如果您有更改,则希望它们以异步方式运行,以便主线程可以返回为用户提供服务。阻止主线程没有任何价值,因为主要上下文已经知道更改,它所做的任何事情都不会影响保存。

最后,使用私有上下文不会“更快”。它会让用户感觉更快,因为他们可以更快地获得控制权。线程与性能没有直接关系。卸载保存的想法是,用户可以返回使用应用程序而不是等待保存。保存很容易,但用户并不在意。

更新

如果您在滚动条上阻止了UI,则表明您的保存时间过长。

点击NSPersistentStoreCoordinator

任何活动会锁定它。长时间获取或长时间保存无关紧要。这是生活中的事实。有办法避免这个规则,但只是抛出问题的线程不会解决它。您需要处理锁定NSPersistentStoreCoordinator的基本问题。

首先,我会考虑将保存拆分为较小的单位。使保存更小允许NSPersistentStoreCoordinator编织保存和提取,以便人类不会感知到保存。

如果您不能这样做,请考虑针对单独的NSPersistentStoreCoordinator执行删除操作。这假设您使用的是SQLite。然后,您可以通过通知将更改反馈给主NSManagedObjectContext。请注意,这些通知的消耗将对主NSManagedObjectContext产生性能影响,因此会影响用户体验。

您还可以在UITableView中探索对象的预发生故障,这样就无需返回磁盘。但是,你正在为内存交易速度/性能,而在iOS上,当你这样做时,你往往会失败。