Вопрос по csv, sqlite, core-data, ios – Какой самый быстрый способ загрузить большой файл CSV в основные данные

6

Conclusion
Проблема закрыта, я думаю.
Похоже, что проблема не имеет ничего общего с методологией, но XCode не правильно очистил проект между сборками.
Похоже, что после всех этих тестов файл sqlite, который использовался, все еще был первым, который не был проиндексирован ......
Остерегайтесь XCode 4.3.2, у меня нет ничего, кроме проблем с очисткой, а не очисткой или добавлением файлов в проект, которые не добавляются автоматически в ресурсы пакета ...
Спасибо за разные ответы ..

Update 3
Поскольку я приглашаю кого-нибудь просто попробовать те же шаги, чтобы увидеть, получают ли они одинаковые результаты, позвольте мне подробно рассказать, что я сделал:
Я начинаю с пустого проекта
Я определил модель данных с одной сущностью, 3 атрибутами (2 строки, 1 с плавающей точкой)
Первая строка проиндексирована
enter image description here

В самом деле Finish LaunchingWithOptions я звоню:

<code>[self performSelectorInBackground:@selector(populateDB) withObject:nil];
</code>

Код для populateDb ниже:

<code>-(void)populateDB{
NSLog(@"start");
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
NSManagedObjectContext *context;
if (coordinator != nil) {
    context = [[NSManagedObjectContext alloc] init];
    [context setPersistentStoreCoordinator:coordinator];
}

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"input" ofType:@"txt"];  
if (filePath) {  
    NSString * myText = [[NSString alloc]
                               initWithContentsOfFile:filePath
                               encoding:NSUTF8StringEncoding
                               error:nil];
    if (myText) {
        __block int count = 0;


        [myText enumerateLinesUsingBlock:^(NSString * line, BOOL * stop) {
            line=[line stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
            NSArray *lineComponents=[line componentsSeparatedByString:@" "];
            if(lineComponents){
                if([lineComponents count]==3){
                    float f=[[lineComponents objectAtIndex:0] floatValue];
                    NSNumber *number=[NSNumber numberWithFloat:f];
                    NSString *string1=[lineComponents objectAtIndex:1];
                    NSString *string2=[lineComponents objectAtIndex:2];
                    NSManagedObject *object=[NSEntityDescription insertNewObjectForEntityForName:@"Bigram" inManagedObjectContext:context];
                    [object setValue:number forKey:@"number"];
                    [object setValue:string1 forKey:@"string1"];
                    [object setValue:string2 forKey:@"string2"];
                    NSError *error;
                    count++;
                    if(count>=1000){
                        if (![context save:&error]) {
                            NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
                        }
                        count=0;

                    }
                }
            }



        }];
        NSLog(@"done importing");
        NSError *error;
        if (![context save:&error]) {
            NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
        }

    }  
}
NSLog(@"end");
}
</code>

Все остальное - код данных ядра по умолчанию, ничего не добавлено.
Я запускаю это в симуляторе.
Я захожу в ~ / Библиотека / Поддержка приложений / iPhone Simulator / 5.1 / Приложения // Документы
Существует файл sqlite, который создается

Я беру это и копирую в свой комплект

Я закомментирую вызов populateDb

Я редактирую persistentStoreCoordinator, чтобы скопировать файл sqlite из пакета в документы при первом запуске

<code>- (NSPersistentStoreCoordinator *)persistentStoreCoordinator 
{
@synchronized (self)
{
    if (__persistentStoreCoordinator != nil)
        return __persistentStoreCoordinator;

    NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"myProject" ofType:@"sqlite"];
    NSString *storePath = [[[self applicationDocumentsDirectory] path] stringByAppendingPathComponent: @"myProject.sqlite"];

    NSError *error;
    if (![[NSFileManager defaultManager] fileExistsAtPath:storePath]) 
    {
        if ([[NSFileManager defaultManager] copyItemAtPath:defaultStorePath toPath:storePath error:&error])
            NSLog(@"Copied starting data to %@", storePath);
        else 
            NSLog(@"Error copying default DB to %@ (%@)", storePath, error);
    }

    NSURL *storeURL = [NSURL fileURLWithPath:storePath];

    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) 
    {

        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }    

    return __persistentStoreCoordinator;
}    
}
</code>


Я удаляю приложение из симулятора и проверяю, что ~ / Library / Application Support / iPhone Simulator / 5.1 / Applications / теперь удалено
Я перестраиваюсь и запускаю снова
Как и ожидалось, файл sqlite копируется в ~ / Library / Application Support / iPhone Simulator / 5.1 / Applications // Documents

However the size of the file is smaller than in the bundle, significantly! Also, doing a simple query with a predicate like this predicate = [NSPredicate predicateWithFormat:@"string1 == %@", string1]; clearly shows that string1 is not indexed anymore

После этого я создаю новую версию модели данных с бессмысленным обновлением, просто чтобы облегчить миграцию
При запуске на симуляторе миграция занимает несколько секунд, размер базы данных увеличивается вдвое, и этот же запрос теперь занимает меньше секунды, а не минуты.
Это решило бы мою проблему, вызвало бы миграцию, но та же самая миграция занимает 3 минуты на iPad и происходит на переднем плане.
Поэтому, где я сейчас нахожусь, лучшим решением для меня все равно было бы предотвращение удаления индексов, а любое другое решение для импорта во время запуска просто занимает слишком много времени.
Дайте мне знать, если вам нужно больше разъяснений ...

Update 2
Таким образом, лучший результат, который у меня был до сих пор, - заполнить базу данных основных данных файлом sqlite, созданным из быстрого инструмента с аналогичной моделью данных, но без индексов, заданных при создании файла sqlite. Затем я импортирую этот sqlite-файл в приложение основных данных с установленными индексами и с учетом облегченной миграции. Для 2 миллионов записей на новом iPad эта миграция займет 3 минуты. Конечное приложение должно иметь 5-кратное количество записей, поэтому мы по-прежнему смотрим на длительное время обработки. Если я пойду по этому пути, то возникнет новый вопрос: можно ли выполнить легкую миграцию в фоновом режиме?

Update
Мой вопрос НЕ в том, как создать инструмент для заполнения базы данных Core Data, а затем импортировать файл sqlite в мое приложение.
 Я знаю, как это сделать, я делал это бесчисленное количество раз.
 Но до сих пор я не осознавал, что такой метод может иметь некоторый побочный эффект: в моем случае индексированный атрибут в результирующей базе данных явно получил «неиндексированный». при импорте файла sqlite таким способом.
Если вы смогли проверить, что какие-либо проиндексированные данные все еще индексируются после такой передачи, мне было бы интересно узнать, как вы поступите, или в противном случае, какова была бы лучшая стратегия для эффективного заполнения такой базы данных.

Original

У меня есть большой файл CSV (миллионы строк) с 4 столбцами, строками и числами с плавающей запятой. Это для iOS-приложения.

Мне нужно, чтобы это было загружено в основные данные при первой загрузке приложения.

Приложение практически не работает до тех пор, пока данные не станут доступны, поэтому время загрузки имеет значение, поскольку первый пользователь, очевидно, не хочет, чтобы приложение загружалось 20 минут, прежде чем оно сможет его запустить.

Прямо сейчас мой текущий код занимает 20 минут на новом iPad, чтобы обработать файл CSV длиной в 2 миллиона строк.

Я использую фоновый контекст, чтобы не блокировать пользовательский интерфейс и сохранять контекст каждые 1000 записей

Первой идеей было создать базу данных на симуляторе, а затем скопировать / вставить ее в папку документа при первом запуске, поскольку это обычный неофициальный способ заполнения большой базы данных. К сожалению, индексы, похоже, не выдерживают такой передачи, и, хотя база данных стала доступна всего через несколько секунд, производительность ужасна, потому что мои индексы были потеряны. Я уже опубликовал вопрос об индексах, но, похоже, на этот вопрос нет хорошего ответа.

Итак, что я ищу, либо:

a way to improve performance on loading millions of records in core data if the database is pre-loaded and moved at first startup, a way to keep my indexes best practices for handling this kind of scenario. I don't remember using any app that requires me to wait for x minutes before first use (but maybe The Daily, and that was a terrible experience). Any creative way to make the user wait without him realizing it: background import while going through tutorial, etc... Not Using Core Data? ...
Очистка не сработала, но перезагрузка ноутбука, ручная очистка всех ссылок на файл и т. Д., Похоже, "решили" эта проблема. странно ... хотя мне также пришлось удалить легкие миграционные линии, чтобы заставить их не мигрировать (так как это займет много минут). В целом, это не чистая реализация, на которую я бы надеялся, но она работает ... пока версия 2 не нуждается в обновлении модели данных, тогда у меня проблемы JP Hribovsek
Так как же вы в итоге "очистили"? чтобы проект работал правильно? lnafziger

Ваш Ответ

2   ответа
0

и мне нужно интегрировать БД в одно из моих приложений, в котором будет много проиндексированных данных в базе данных SQLite. Я надеялся, что смогу сделать какой-нибудь метод, с помощью которого я смогу массово вставить свою информацию в файл SQLite и добавить этот файл в свой проект. После обнаружения и прочтения вашего вопроса, предоставленного ответа и многочисленных комментариев я решил проверить источник SQLite, чтобы узнать, смогу ли я решить эту проблему.

Первоначально я думал, что реализация SQLite для iOS фактически исключает ваши индексы. Причина в том, что вы изначально создаете свой индекс БД в системе x86 / x64. IOS является процессором ARM, а числа обрабатываются по-разному. Если вы хотите, чтобы ваши индексы были быстрыми, вы должны сгенерировать их таким образом, чтобы они были оптимизированы для процессора, в котором они будут искать.

Поскольку SQLite предназначен для нескольких платформ, с него можно было бы отбросить все индексы, созданные в другой архитектуре, и перестроить их. Однако, поскольку никто не хочет ждать перестроения индекса при первом обращении к нему, разработчики SQLite, скорее всего, решили просто удалить индекс.

После изучения кода SQLite я пришел к выводу, что этоmost likely происходит. Если бы не причина архитектуры процессора, я нашел код (см.analyze.c и другая метаинформация вsqliteint.h) где индексы были удалены, если они были созданы в неожиданном контексте. Я догадываюсь, что контекст, который управляет этим процессом, - это то, как базовая структура данных b-дерева была построена для существующего ключа. Если текущий экземпляр SQLite не может использовать ключ, он удаляет его.

Стоит отметить, что iOS Simulator - это всего лишь симулятор. Это не эмулятор аппаратного обеспечения. Таким образом, ваше приложение работает на псевдо-iOS-устройстве на процессоре x86 / x64.

Когда ваше приложение и база данных SQLite загружаются на ваше устройство iOS, загружается вариант, скомпилированный ARM, который также ссылается на скомпилированные библиотеки ARM в iOS. Я не смог найти специфичный для ARM код, связанный с SQLite, поэтому я полагаю, что Apple пришлось изменить его в соответствии с их требованиями. Это также может быть частью проблемы. Это может быть не проблема с кодом root-SQLite, а проблема с компилированным вариантом Apple / ARM.

Единственное разумное решение, которое я могу придумать, - это то, что вы можете создать приложение-генератор, которое вы запускаете на своей машине с iOS. Запустите приложение, создайте ключи и скопируйте файл SQLite с устройства. Я предполагаю, что такой файл будет работать на всех устройствах, поскольку все процессоры ARM, используемые iOS, являются 32-битными.

Опять же, этот ответ является немного обоснованным предположением. Я собираюсь пометить ваш вопрос как SQLite. Надеюсь, гуру может найти это и сможет взвесить этот вопрос. Я действительно хотел бы знать правду в свою пользу.

6

используя автономное приложение (скажем, утилиту командной строки), написанное на Cocoa, работающее в OS X и использующее ту же платформу Core Data, что и iOS. Вам не нужно беспокоиться о "выживших индексах" или что-то еще - на выходе генерируется файл базы данных .sqlite, сгенерированный Core Data, непосредственно и сразу же используемый приложением iOS.

Пока вы можете производить генерацию БД в автономном режиме, это пока лучшее решение. Я успешно использовал эту технику для предварительной генерации баз данных для развертывания iOS самостоятельно. Проверьте мои предыдущие вопросы / ответы для более подробной информации.

Я тоже сделал то же самое без проблем с индексами ....
@nafziger, вы имеете в виду, что у вас есть индексы в вашей базовой модели данных, и что вы точно знаете, что эти индексы все еще работают так, как они должны работать после повторного использования этого файла sqlite? Если да, то какова была ваша методология, чтобы убедиться, что ваши индексы все еще работают? JP Hribovsek
Возможно, я не описал это правильно, но позвольте мне уточнить, что я сделал: я использовал мой загрузчик CSV в код основных данных на симуляторе. В том же приложении (та же модель данных ядра) я удалил файл sqlite из данных симулятора, переместил его в пакет и отредактировал код, чтобы больше не выполнять импорт CSV, а просто повторно использовал файл sqlite из пакета. Я не говорю, что он не работает по внешнему виду, он «работает», так как я могу запрашивать и получать свои результаты. Но sqlite, который изначально был 200Mb, и все еще тот размер, когда был перемещен в пакет, стал файлом 120Mb, и производительность показывает, что мои индексы исчезли JP Hribovsek
Что вы имеете в виду, мне не нужно беспокоиться о своих показателях; Как я уже говорил в своем вопросе, я сделал этот точный метод, выходной файл представлял собой файл базы данных sqlite (200 МБ), и при использовании в моем приложении с точно такой же моделью файл уменьшился до 110 МБ, и производительность явно показала, что мои индексы не были за работой. Так что я действительно беспокоюсь о своих показателях, вот и весь смысл! JP Hribovsek
@JP Hribovsek существует различие между простым старым файлом базы данных SQLite, сгенерированным SQLite (который, как правило, напрямую не используется Core Data), и файлом базы данных SQLite, сгенерированным Core Data. Я использовал предложенную мной систему, используя точно такую же модель данных Core Data, как в приложении для iOS, так и в служебной программе командной строки OS X Cocoa, без проблем. Я также предварительно сгенерировал БД SQLite, используя SQLite, для использования в приложении iOS, использующем SQLite, без проблем - но это было до того, как Core Data поступила на iOS, что значительно упростило эти проблемы.

Похожие вопросы