Есть ли способ "заправить насос" в FMDB, чтобы он был готов к работе быстрее

У нас есть база данных sqlite, в которой наше приложение для iOS хранит изображения в столбце BLOB-объектов. Мы используем FMDB, чтобы прочитать BLOB-объект как NSData, а затем преобразовать в UIImage. Код показан ниже.

-(UIImage*)getImageWithGuid:(NSString *)guid imageSizeKind:(ImageSizeKind)imageSizeKind
    {
        FMDatabase *db = [self openFMDatabase];
        if (!db) {
            return nil;
        }

        NSData *imageData = nil;
        NSString *query = [NSString stringWithFormat:@"SELECT Image FROM images WHERE Guid = '%@' AND MediaType = %d limit 1", guid, imageSizeKind];

        FMResultSet *rs = [db executeQuery:query];

        if ([rs next])
        {
            imageData = [rs dataForColumn:imagesTable.image];
        }

        [rs close];
        [db close];

        if (!imageData) {
            NSLog(@"Image was not found in database '%@' using sql query '%@'", [self databasePath], query);
        }

        UIImage *image = [UIImage imageWithData:imageData];

        return image;
    }

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


23: 31: 17.084 Получено изображение за 4.208354 с
23:31:17.086 Изменение размера изображения за 0,001961 с
23:31:17.115 Полученное изображение за 0,028943 с
23: 31: 17.117 Изменение размера изображения за 0,001891 с
23: 31: 17.131 Полученное изображение за 0,013373 с
23: 31: 17.133 Изменение размера изображения за 0,002036 с
23: 31: 17.844 Полученное изображение за 0,711072 с
23:31:17.846 Изменение размера изображения за 0.001634 сек.
23: 31: 17.880 Получено изображение за 0,034076 с
23: 31: 17,882 Изменение размера изображения за 0,001678 с
23: 31: 17,910 Полученное изображение за 0,028255 с
23: 31: 17,912 Изменение размера изображения за 0,001652 с
23: 31: 17,943 Полученное изображение за 0,031323 с
23: 31: 17,945 Изменение размера изображения за 0,001783 с
23: 31: 17,954 Получено изображение за 0,009396 с
23: 31: 17,956 Изменение размера изображения за 0,001982 с
23: 31: 17,986 Полученное изображение за 0,029724 с
23: 31: 17,988 Изменение размера изображения за 0,001977 с
23: 31: 18.026 Полученное изображение за 0.037283 сек.
23: 31: 18,027 Изменение размера изображения за 0,001837 с
23: 31: 18.051 Полученное изображение за 0,023700 с
23: 31: 18,053 Изменение размера изображения за 0,001947 с
23: 31: 18.088 Получено изображение за 0,035087 с
23: 31: 18,090 Изменение размера изображения за 0,001687 с
23: 31: 18.136 Полученное изображение за 0,045304 с


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

Есть ли какой-нибудь способ, которым я могу "прокачать насос", так сказать, чтобы убрать эти 4,2 секунды и получить базу данных, готовую вести себя так же, как и со всеми последующими изображениями. В идеале было бы здорово отложить эту 4-секундную задержку в некотором фоновом потоке, чтобы пользователю не приходилось испытывать ее в какой-то другой точке приложения, просто переместив эти первые 4 секунды в другое место, которое может возникнуть.

Благодарю.

1 ответ

Решение

FMDB (или, точнее, SQLite) не требует "заправки". Я просто запустил следующий код:

CFAbsoluteTime last = CFAbsoluteTimeGetCurrent();

for (NSInteger i = 0; i < 10; i++) {
    FMResultSet *rs = [database executeQuery:@"select image_data from images where guid = ?", @(i)];
    NSAssert(rs, @"select failed: %@", [database lastErrorMessage]);
    if ([rs next]) {
        CFAbsoluteTime current = CFAbsoluteTimeGetCurrent();
        NSData *data = [rs dataForColumnIndex:0];
        NSLog(@"%lu %0.3f", (unsigned long)[data length], current - last);
        last = current;
    }
    [rs close];
}

А на iPhone 6+ сообщалось:

2016-03-24 21: 53: 36.107 MyApp [3710: 1262147] 1000000 0,010
2016-03-24 21:53:36.112 MyApp[3710:1262147] 1000000 0,004
2016-03-24 21:53:36.115 MyApp[3710:1262147] 1000000 0,004
2016-03-24 21:53:36.123 MyApp[3710:1262147] 1000000 0,008
2016-03-24 21:53:36.131 MyApp[3710:1262147] 1000000 0,008
2016-03-24 21:53:36.138 MyApp[3710:1262147] 1000000 0,007
2016-03-24 21:53:36.146 MyApp[3710:1262147] 1000000 0,007
2016-03-24 21:53:36.153 MyApp[3710:1262147] 1000000 0,007
2016-03-24 21:53:36.161 MyApp[3710:1262147] 1000000 0,007
2016-03-24 21:53:36.168 MyApp[3710:1262147] 1000000 0,007

Как вы, вероятно, можете сделать вывод из журнала выше, это был бенчмаркинг извлечения десяти 1 МБ BLOB из базы данных.

Итак, есть несколько возможных проблем:

  • Дважды проверьте, где вы начинаете свой таймер. Убедитесь, что у вас нет другого кода между началом таймера и началом извлечения изображений. Возможно, в вашем коде есть что-то еще, что замедляет работу приложения.

    Я мог бы предложить запустить "Time Profiler" от Instruments и точно подтвердить, что объясняет вашу 4-секундную задержку. Или, что немного сложнее, иногда вы можете определить блокирующие вызовы, отсортировав "время ожидания" в "Системных вызовах" в инструменте "Отслеживание системы". Но, в заключение, подтвердите, что SQLite на самом деле является источником 4-секундной задержки.

    Если вы не знакомы с инструментами, вы часто можете идентифицировать "горлышко бутылки" старомодным способом, вставляя все больше и больше временных отчетов и сужая их до одной или двух строк, которые учитывают вашу 4-секундную задержку. (Например, это executeQuery, или stepили изменение размера изображения, а не поиск изображения, или, возможно, первое открытие базы данных делает какую-то дорогую копию из пакета в документы и т. д.)

  • Если проблема действительно лежит в SQLite, я бы удостоверился, что у вас есть индекс на guid (в идеале уникальный ключ). Это вряд ли может вызвать эту проблему (особенно если вы закрываете базу данных перед открытием следующей строки), но если задержка производительности действительно в SQLite, это может помочь.

    Также я замечаю, что вы используете limit 1 в вашем заявлении SQL. Это правда, что у вас есть несколько записей с одинаковым guid (обычно это было бы уникально)? Возможно, разница во времени связана с количеством совпадений, которые вы получаете за первый ключ.

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

  • По наблюдениям Росомахи, что лучше хранить изображения в файловой системе и хранить только имена файлов в базе данных, это определенно верно (если я правильно помню, в последний раз, когда я сравнивал, это происходило на 10-20% медленнее при извлечении изображений из SQLite, чем получить имя файла из SQLite, но затем получить изображение непосредственно из файловой системы). Старое эмпирическое правило гласило, что если ваши изображения имеют размер миниатюр, их хранение в SQLite хорошо, но если они имеют размер в мегабайтах (а не 10 с КБ), то их хранение в файловой системе начинает приводить к материальной производительности. улучшения. Я не думаю, что это отвечает на вопрос, почему ваш первый звонок такой медленный, но стоит подумать, если изображения будут больше.

    Если вы ищете переполнение стека для [sqlite] blob performance или выполнить аналогичный поиск в Google, вы увидите много дискуссий о недостатках хранения очень больших объектов BLOB в SQLite.

Сказав это, у меня есть одно наблюдение, связанное с производительностью: я бы не открывал и не закрывал базу данных каждый раз. При запуске приложения откройте базу данных и оставьте ее открытой. Это экономит только 10 секунд миллисекунд, поэтому это не будет причиной вашей проблемы с задержкой в ​​4 секунды, но будет немного более эффективной.

Но, суть в том, что я не могу воспроизвести задержку производительности, о которой вы сообщаете. Если вы можете создать небольшой автономный MCVE, мы поможем вам его диагностировать. Но я подозреваю, что проблема кроется где-то кроме фрагмента кода, которым вы поделились с нами до сих пор.

Другие вопросы по тегам