Есть ли способ ускорить этот код ввода/вывода файла?

В моем приложении у меня около 300 NSData объектов размером 0,5 МБ, и я записываю их последовательно в файл по существу с этим кодом (который записывает один объект размером 0,5 МБ 300 раз):

- (void)createFile {

    // create .5 MB block to write
    int size = 500000;
    Byte *bytes = malloc(size);
    for (int i = 0; i < size; i++) {
        bytes[i] = 42;
    }
    NSData *data = [NSData dataWithBytesNoCopy:bytes length:size 
       freeWhenDone:YES];

    // temp output file
    NSUUID *uuid = [NSUUID UUID];
    NSString *path = [[NSTemporaryDirectory() 
        stringByAppendingPathComponent:[uuid UUIDString]] 
        stringByAppendingPathExtension:@"dat"];
    NSOutputStream *outputStream = [[NSOutputStream alloc] 
        initToFileAtPath:path append:NO];
    [outputStream open];

    double startTime = CACurrentMediaTime();

    NSInteger totalBytesWritten;
    NSInteger bytesWritten;
    Byte *readPtr;

    for (int i = 0; i < 300; i++) {

        // reset read pointer to block we're writing to the output
        readPtr = (Byte *)[data bytes];
        totalBytesWritten = 0;

        // write the block
        while (totalBytesWritten < size) {
            bytesWritten = [outputStream write:readPtr maxLength:size 
                - totalBytesWritten];
            readPtr += bytesWritten;
            totalBytesWritten += bytesWritten;
        }

    }

    double duration = CACurrentMediaTime() - startTime;
    NSLog(@"duration = %f", duration);

    [outputStream close];

}

Как на моем iPod (5-го поколения), так и на моем iPhone 6, этот процесс занимает около 3 секунд, и мне было интересно, есть ли какой-нибудь более быстрый способ сделать это. Я пробовал использовать подходы NSFileManager и NSFileHandle, но они занимают примерно один и тот же отрезок времени, что приводит меня к предположению, что это фундаментальный предел ввода-вывода, в котором я работаю.

Есть ли способ сделать это быстрее (этот код должен компилироваться и запускаться на любом устройстве)?

Ответы

Ответ 1

Здесь самая высокая производительность, которую я смог достичь, используя mmap и memcpy.

Требуется в среднем около 0,2 секунды для запуска на моем iPhone 6 с некоторым изменением примерно до 0,5 с. YMMV, однако, как представляется, iPhone 6 имеет два разных поставщика флэш-памяти: один - это TLC, а другой - MLC - те, у кого есть TLC, получат значительно лучшие результаты.

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

- (IBAction)createFile {
  NSData *data = [[self class] dataToCopy];

  // temp output file
  NSUUID *uuid = [NSUUID UUID];
  NSString *path = [[NSTemporaryDirectory()
                     stringByAppendingPathComponent:[uuid UUIDString]]
                    stringByAppendingPathExtension:@"dat"];

  NSUInteger size = [data length];
  NSUInteger count = 300;

  NSUInteger file_size = size * count;

  int fd = open([path UTF8String], O_CREAT | O_RDWR, 0666);
  ftruncate(fd, file_size);

  void *addr = mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

  double startTime = CACurrentMediaTime();
  static dispatch_queue_t concurrentDataQueue;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    concurrentDataQueue = dispatch_queue_create("test.concurrent", DISPATCH_QUEUE_CONCURRENT);
  });

  for (int i = 0; i < count; i++) {
    dispatch_async(concurrentDataQueue, ^{
      memcpy(addr + (i * size), [data bytes], size);
    });
  }

  dispatch_barrier_async(concurrentDataQueue, ^{
    fsync(fd);

    double duration = CACurrentMediaTime() - startTime;
    NSLog(@"duration = %f", duration);

    munmap(addr, file_size);
    close(fd);
    unlink([path UTF8String]);
  });
}

Ответ 2

300 * 0,5/3 = 50 МБ/с для 150 МБ данных выглядит достаточно быстро. Полагаю, вы нажмете ограничение скорости WRITE для флэш-памяти. Я считаю, что вы запускаете этот код в фоновом потоке, поэтому проблема заключается не в блокировании пользовательского интерфейса

Ответ 3

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

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

Приложения могут вызывать функцию BSD fcntl с флагом F_NOCACHE, чтобы включить или отключить кеширование для файла. Дополнительные сведения об этой функции см. В fcntl. "~ Рекомендации по производительности

или "прочитать много или все данные в памяти перед его обработкой" ~ Рекомендации по производительности

iPhone 6 использует 16-гигабайтную флэш-память hynix для SK ~ [Teardown], а теоретический предел для последовательной записи составляет от 40 до 70 Мбит/с ~ [NAND flash].