iOS CoreData: производительность NSFetchedResultsController - PullRequest
1 голос
/ 26 сентября 2010

В модели у меня есть две сущности: запись и категория.Категория один-ко-многим с записью через обратную связь.Постоянное хранилище имеет тип SQLITE, а база данных не так уж мала, около 23 МБ (17k записей).

Я использую детальный дизайн списка, чтобы показать таблицу записей и детальный просмотр записей. Представление listController используетNSFetchedResultsController.

Сборка на устройстве, если я не использую setFetchBatchSize:

CoreData: annotation: sql время выборки соединения: 15,8800s CoreData: annotation: общее время выполнения выборки:16,9198 для 17028 строк.

OMG!

Если я использую setFetchBatchSize: 25, все снова отлично работает:

CoreData: annotation: sql connectionвремя выборки: 1,1736 с CoreData: аннотация: общее время выполнения выборки: 1,1900 с для 17028 строк.

Да, это было бы здорово!Но это не так!В списке viewController, когда пользователь нажимает на запись, я выделяю подробный viewController и передаю запись в indexPath в fetchedResultsController:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Record *record = (Record *)[fetchedResultsController objectAtIndexPath:indexPath];
RecordViewController *recordViewController= [[RecordViewController alloc] init];
    recordViewController.record = record;
    [self.navigationController pushViewController:recordViewController animated:YES];
    [recordViewController release];
}

СЕЙЧАС, в подробном viewController у меня есть кнопка для установкизапись как любимая или нет:

- (IBAction) setFavorite {
if (![record.ISFAV intValue]) 
[record setValue:[NSNumber numberWithInt:1] forKey:@"ISFAV"];

else 
[record setValue:[NSNumber numberWithInt:0] forKey:@"ISFAV"];

###SAVE ON THE CONTEXT HERE###

}

ОК, вы готовы?Если я нажимаю на первую запись в списке, то добавляю или удаляю ее из избранного, это происходит за 0,0046 секунды, мгновенно!Консоль с режимом отладки SQL показывает только оператор UPDATE:

CoreData: sql: BEGIN EXCLUSIVE CoreData: sql: UPDATE ZRECORD SET ZISFAV =?, Z_OPT =?ГДЕ Z_PK =?И Z_OPT =?CoreData: sql: COMMIT CoreData: аннотация: sql время выполнения: 0,0046 с

Если я прокручиваю большой список очень быстро (и, очевидно, нахожу пакетные запросы на консоли), когда я нажимаю на записьдостигнуто со многими пакетными запросами, и я добавляю \ удаляю его из избранного, много много много много (слишком много! чем больше я прокручиваю, тем больше они!) операторы SELECT появляются в консоли перед словом ОБНОВЛЕНИЕЭто означает, что общее время выполнения недопустимо (uibutton долго зависает на iphone).

Что происходит?Проблема явно связана с пакетными запросами на выборку.Больше запросов на выборку = больше операторов SELECT перед оператором UPDATE.Это один из них:

CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZCONTENT, t0.ZCONTENT2, t0.ZISUSER, t0.ZISFAV, t0.ZTITLE,t0.ZTITLE2, t0.ZID, t0.ZAUTHOR, t0.ZCATEGORY ОТ ZRECORD t0 ГДЕ t0.Z_PK IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ORDER BY t0.ZTITLE LIMIT 26

Если я удалю setFetchBatchSize, проблем не будет (но запуск требует 16 секунд).Кажется, что когда я обновляю свойство ISFAV, CoreData необходимо снова выполнить все fetchRequests, которые были необходимы для достижения этой записи, даже если я передаю эту запись в подробный viewController как объект.

Извините за длинный постЯ старался быть как можно более понятным.Большое спасибо, я схожу с ума ...

Ответы [ 2 ]

3 голосов
/ 26 сентября 2010

Происходит то, что контроллер выбранных результатов видит уведомление об изменении при обновлении управляемого объекта и должен выяснить, каким индексом был этот объект, чтобы он мог сообщить своему делегату, что объект был обновлен.Для этого он снова проходит через все выборки, пока не найдет нужный.Я не уверен, почему он не может просто кэшировать эту информацию, но, очевидно, это не так.Вы пробовали добавить кеш секций в контроллер полученных результатов?Это может ускорить процесс (в зависимости от того, использует ли выбранный контроллер результатов этот кэш в этом случае).Вы делаете это просто путем указания имени кэша при вызове -initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:.

2 голосов
/ 26 сентября 2010

Во-первых, я бы посоветовал не показывать пользователю большую таблицу с 17K-записями; Вместо этого вы должны разрешить пользователю выполнять поиск записей, а затем выбирать один из результатов поиска. В любом случае, если вы хотите, чтобы пользователь мог выбрать запись непосредственно из большой таблицы, вам нужно подумать о процессе извлечения.

Начните проверять, правильно ли вы проиндексировали в своей базовой модели данных атрибуты, которые вы используете для настройки связи NSPredicate с вашим NSFetchedResultsController. Подумайте о размере вашего «рабочего набора» записей. Это должно быть как можно меньше и обычно порядка сотен записей.

В вашем случае установка setFetchBatchSize на 25, вероятно, не подходит, учитывая, что вы хотите, чтобы ваш пользователь просматривал записи 17K. Начиная с 17000: 25 = 680, вам потребуется столько выборок, чтобы получить последние 25 записей. Но выборка включает в себя фактический ввод-вывод в базу данных, чтобы убедиться, что все всегда синхронизировано с другими «возможными» операциями вставки / удаления / обновления, выполняемыми другими потоками.

Даже если ваше приложение не использует несколько потоков с Базовыми данными, инфраструктура Базовых данных должна проверить, чтобы убедиться, что что-то изменилось. Теперь, поскольку ввод-вывод дорог, вам нужен компромисс. Установка setFetchBatchSize на 1000 потребует в худшем случае 17 выборок для достижения последних 1000 записей (улучшение в 40 раз), даже если каждая отдельная выборка может занять «немного» больше времени.

Использование кэша, как предложено, может дать некоторое преимущество, если другие потоки не изменят данные. Действительно, попадания в кэш быстрые, очень быстрые. Однако промахи в кеше чрезвычайно дороги и требуют ввода-вывода для извлечения связанных данных из базы данных. Вероятность потери кэша увеличивается, конечно, когда несколько потоков работают одновременно в одной базе данных (если только эти потоки не только читают записи).

Еще одно возможное решение, которое вы можете попробовать, заключается в использовании нескольких потоков. Ваш основной поток извлекает только начальное количество записей и представляет их пользователю, в то время как другой поток, использующий другой контекст управляемого объекта, асинхронно извлекает другой пакет записей в фоновом режиме (используя правильное смещение). Эти записи затем передаются в основной поток для визуализации.

Еще одна вещь. Вы не должны использовать KVC для обновления значения ваших атрибутов; по соображениям производительности гораздо лучше сделать что-то вроде

record.ISFAV =  [NSNumber numberWithInt:1];

или

[record setISFAV:[NSNumber numberWithInt:1]];

Обновляя только один атрибут, вы можете не заметить разницы, но если вам нужно работать с несколькими атрибутами, вы можете начать получать огромную экономию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...