核心数据并发导入性能



我正在开发一个连接到web服务的应用程序,该应用程序在应用程序启动期间检索大量数据。我使用并发来避免UI阻塞。我选择了以下核心数据堆栈模式:后台专用moc-->主moc-->编写器专用moc-->协调器-->文件。

导入操作时会出现此问题。CPU使用率为100%,应用程序在使用过程中速度变慢。我处理了300个对象的批,总共导入了大约10000个对象。

对于每个批次,都会创建一个NSOperation,其中包含一个关联的临时moc,即后台moc的子级。操作在NSOperationQueue中排队。导入作业完成后,应用程序会变得更慢,具体取决于运行的作业数量。我还注意到,当应用程序被终止并重新启动时,它的可用性和速度确实要高得多。

导入时,我的内存占用在40Mo和60Mo之间变化。你觉得太多了吗?

你认为我的堆叠模式适合我的需要吗?我应该迁移到具有2个协调器的堆栈吗?

此外,当获取数据以在tableView中显示时,我应该在显示视图之前立即使用performBlockAndWait获取数据吗?

感谢您的帮助

所描述的堆栈很好。

CPU使用可能会产生误导。你要确保你不在主线程上,因为这会导致你在应用程序中的大部分速度缓慢和/或断断续续。

当您在Instruments中观看应用程序时,什么最耗时?在主队列上花费了多少时间?

一般来说,导入不应该导致CPU处于100%。如果您在后台线程中执行此操作,则很可能会获得一些性能提升。

如果应该分享你的导入代码和/或仪器跟踪,这样我就可以看到发生了什么。

我认为您的设置有问题。您声明后台托管对象上下文的子线程是主线程,并创建要导入的子线程。这必然会导致UI故障。

此外,我认为依赖NSOperation是不必要的。您应该使用NSManagedObjectContext块API。

我建议的设置是:

RootContext (background, writing to persistent store) -> parent of
MainContext (foreground, UI) -> parent of
WorkerContext(s) (background, created and discarded ad hoc)

您可以在web调用的回调中创建辅助上下文,以完成导入的繁重工作。确保您使用的是块API,并将所有对象限制在本地上下文中。save上下文将更改推送到主线程(在将数据保存到存储之前,主线程已经开始显示数据),并定期保存主上下文和编写器上下文,始终使用bock API。

一个典型的saveContext函数,可以称为线程安全(这里self指的是数据管理器单例或应用程序委托):

func saveContext () {
    if self.managedObjectContext.hasChanges  {
       self.managedObjectContext.performBlocAndWait {
            do { try self.managedObjectContext.save() }
            catch let error as NSError {
                print("Unresolved error while saving main context (error), (error.userInfo)")
            }
        }
        self.rootContext.performBlockAndWait {
            do { try self.rootContext.save() }
            catch let error as NSError {
                print("Unresolved error while saving to persistent store (error), (error.userInfo)")
            }
        }
    }
}

经过几天的测试和仪器跟踪,我可以给你更多的细节。以下片段显示了如何从共享实例中保存上下文(基于父/子模式):

- (void)save {
    [self.backgroundManagedObjectContext performBlockAndWait:^{
        [self saveContext:self.backgroundManagedObjectContext];
        [self.mainManagedObjectContext performBlock:^{
            [self saveContext:self.mainManagedObjectContext];
            [self.writerManagedObjectContext performBlock:^{
                [self saveContext:self.writerManagedObjectContext];
            }];
        }];
     }];
}
- (void)saveContext:(NSManagedObjectContext*)context {
     NSError *error = nil;
     if ([context hasChanges] && ![context save:&error]) {
         NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
     }
}

然后,由于同步操作,每个导入作业都在后台上下文中执行。以下方法在操作主中激发。

- (void)operationDidStart
{
    NSManagedObjectContext *moc = self.context;
    NSMutableArray *insertedOrUpdatedObjects = [NSMutableArray array];
    NSMutableArray *subJSONs = [NSMutableArray array];
    NSUInteger numberOfJobs = ceil((double)self.JSONToImport.count/self.batchSize);
    for (int i = 0; i < numberOfJobs; i++) {
        NSUInteger startIndex = i * self.batchSize;
        NSUInteger count = MIN(self.JSONToImport.count - startIndex, self.batchSize);
        NSArray *arrayRange = [self.JSONToImport subarrayWithRange:NSMakeRange(startIndex, count)];
        [subJSONs addObject:arrayRange];
    }
    __block NSUInteger  numberOfEndedJobs = 0;
    for (NSArray *subJSON in subJSONs) {
        [moc performBlock:^{
            [self startJobWithJSON:subJSON context:moc completion:^(NSArray *importedObjects, NSError *error) {
                numberOfEndedJobs++;
                if (!error && importedObjects && importedObjects.count > 0) {
                    [insertedOrUpdatedObjects addObjectsFromArray:importedObjects];
                }
                if (numberOfEndedJobs == numberOfJobs) {
                    [[CoreDataManager manager] save];
                    if (self.operationCompletion) {
                        self.operationCompletion(self, insertedOrUpdatedObjects, error);
                    }
                }
            }];
        }];
    }
}

正如你所看到的,我把我的进口分成批次(500个)。操作在后台上下文队列上执行每个批处理,并在所有批处理结束时保存堆栈。

由于时间档案器,保存方法似乎为每个线程占用了23%的CPU使用量。

希望尽可能清楚。

最新更新