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

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

Моим вторым выбором было создание таблицы SQLite для управления им. (Используя fmdb)

Решил написать простой тест на задержку. В следующем тесте я получаю доступ к 500 атрибутам файла и 500 записям sqlite:

- (void)latencyTest
{
    NSMutableArray *arrayTest1 = [[NSMutableArray alloc]init];
    NSMutableArray *arrayTest2 = [[NSMutableArray alloc]init];
    FMResultSet *results = [_database executeQuery:@"SELECT * FROM `tb_media`"];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"dd-MM-yyyy HH:mm:ss:SSS"];
    NSLog(@"Time1: %@",[formatter stringFromDate:[NSDate date]]);
    int i=1;
    while(i<501)
    {
        NSString *test = [NSString stringWithFormat:@"%@/_media/media/19/%d.jpg",_outputPath,i];
        NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:test error:nil];
        NSDate *dateX = [attributes fileModificationDate];
        [arrayTest1 addObject:dateX];
        i++;
    }
    NSLog(@"Time2: %@",[formatter stringFromDate:[NSDate date]]);
    while([results next])
    {        
        NSDate *myDate = [NSDate dateWithTimeIntervalSince1970:[results intForColumn:@"last_update"]];
        [arrayTest2 addObject:myDate];
    }
    NSLog(@"Time3: %@",[formatter stringFromDate:[NSDate date]]);
}

Полученные результаты:

    //iPhone 5 (Actual Device) 500 Pics

    Files Start:                 05-03-2014 09:31:20:375 
    Files End & Sqlite start:    05-03-2014 09:31:20:491 
    Sqlite end:                  05-03-2014 09:31:20:507

    Files Start:                 05-03-2014 09:31:56:305 
    Files End & Sqlite start:    05-03-2014 09:31:56:421 
    Sqlite end:                  05-03-2014 09:31:56:437

    Files Start:                 05-03-2014 09:32:19:053 
    Files End & Sqlite start:    05-03-2014 09:32:19:170 
    Sqlite end:                  05-03-2014 09:32:19:187

Как видите, результаты почти такие же. Мои вопросы:

  1. Я исходил из предположения, что доступ к одному файлу за раз с помощью attributesOfItemAtPath приведет к занимает намного больше времени, чем sql. Я что-то пропустил?

  2. Действительно ли attributesOfItemAtPath обращается к файлу или iOS? файловая система хранит все атрибуты в какой-то базе данных для легкий доступ?

  3. Увидев приведенные выше результаты, я решил пойти с attributesOfItemAtPath метод. Есть что-нибудь еще, чего я не рассматривая переход на sqlite?

7
Segev 5 Мар 2014 в 12:49

2 ответа

Лучший ответ

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

1) Вы не учли время, затраченное на NSLog и цикл while. 75% времени занимают они тогда как вы просто хотите сравнить intForColumn и attributesOfItemAtPath. Правильный способ сделать это - запустить инструменты Timer Profiler. и сравните время получения одной записи.

2) Вы использовали FMDB как файловый менеджер. Внутренне FMDB сериализует данные в файл. Ядро FMDB / SQL Lite заключается в его структуре данных, специально индексируемой, которую вы вообще не использовали. Таким образом, даже если вы сравните время, затраченное на ваши записи, вы заметите, что FMDB занимает больше времени, чем файловый менеджер, из-за дополнительных накладных расходов на сериализацию данных в определенном формате.

3) Время доступа X записей сравнивается с количеством раз, когда доступ был сделан к диску (жесткому диску), а не к куче. В обоих случаях вы делаете доступ к куче хранилища данных. Так что вы вообще не увидите никакой разницы.

Означает ли это, что File Manager лучше FMDB, абсолютно нет !! Вот несколько причин, почему:

FMDB работает хорошо только тогда, когда он настроен на это. Ядро FMDB состоит в двух аспектах разбиения на страницы (кэширование в кучу) и индексации. Позвольте мне объяснить вам по очереди.

1) Предположим, вы пытаетесь получить доступ к отметке времени для 100 изображений. Где каждое изображение имеет 1000 отметок времени. Это означает, что вам нужно сделать 100 * 1000 = 100 000 доступа к хранилищу данных. Если изображения маленькие, тогда Filemanager загрузит файл в кучу, и доступ будет быстрее, чем FMDB, но если у вас недостаточно места в куче, ваше приложение выдаст предупреждение о памяти и будет обращаться к файлу с диска, а не с кеша, что значительно медленнее.

Таким образом, это двоичное состояние либо все из кучи, либо все с диска

FMDB превосходит это состояние и извлекает частичные записи в зависимости от доступного пространства кучи. Это ускоряет доступ, когда у вас огромное количество записей.

Идеальный способ проверить этот сценарий - запустить ваш fucntion latencyTest как минимум для 10 000 изображений (не с отметкой времени). Таким образом, время записи и скорость итераций будут незначительны по сравнению с общим затраченным временем.

2) Структура индекса, восходит к основам SQL Lite. Возможно, вы захотите добавить дополнительный вызов атрибута в качестве количества доступа к изображениям и проиндексировать свою таблицу на этом. Это значительно повысит силу. Это не совсем возможно с Filemeanger.

Решение, которое я рекомендую.
1) Если у вас есть данные менее 2 МБ (изображения плюс временная метка), перейдите в Filemenager.

2) Если объем данных> 2 МБ, выберите Core Data / FMDB.

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

5
Kunal Balani 14 Мар 2014 в 15:41

Во-первых, как сказал @kunal, ваш метод тестирования не является детерминированным и может ввести в заблуждение ваши решения.

Сказав это, attributeOfItemAtPath: выполняет небольшие накладные расходы, если вам нужна только измененная дата, и производительность действительно является проблемой для вас. Что вы можете сделать, так это использовать lstat вместо этого. Это похоже на ваш случай (обратите внимание, что я удалил массивы, чтобы избежать ненужных накладных расходов на тесте):

#import <sys/stat.h>

- (void)latencyTest
{
    // *********************** Test 1 ***********************************//
    double t = CACurrentMediaTime();
    for (NSInteger i = 1; i < 748; i++)
    {
        NSString *path = [NSString stringWithFormat:kMediaPath, i];
        NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path
                                                                                    error:nil];
        NSDate *dateX = [attributes fileModificationDate];
    }
    double total = CACurrentMediaTime() - t;
    NSLog(@"Total time fileManager: %fs, average per read: %fs", total, total / 747.f);

    // *********************** Test 2 ***********************************//
    struct stat linfo;
    t = CACurrentMediaTime();
    for (NSInteger i = 1; i < 748; i++)
    {
        NSString *path = [NSString stringWithFormat:kMediaPath, i];
        lstat([path cStringUsingEncoding:NSUTF8StringEncoding], &linfo);
        NSDate *dateX = [NSDate dateWithTimeIntervalSince1970:linfo.st_mtime];
    }
    total = CACurrentMediaTime() - t;
    NSLog(@"Total time lstat: %fs, average per read: %fs", total, total / 747.f);
}

Для ввода 748 изображений мои результаты:

// Simulator (iOS 7.1)

Total time fileManager: 0.061365s, average per read: 0.000082s
Total time lstat: 0.004313s, average per read: 0.000006s

// iPhone 5s Device (iOS 7.1)

Total time fileManager: 0.019299, average per read: 0.000026
Total time lstat: 0.008520, average per read: 0.000011
0
fz. 15 Мар 2014 в 04:13