Grand Central Стратегия открытия нескольких файлов - PullRequest
4 голосов
/ 26 декабря 2010

У меня есть рабочая реализация, использующая очереди диспетчеризации Grand Central, которые (1) открывают файл и вычисляют хеш OpenSSL DSA для «queue1», (2) записывают хэш в новый файл «side car» для последующей проверки на"queue2".

Я хотел бы открыть несколько файлов одновременно, но на основе некоторой логики, которая не "душит" ОС, поскольку открыты сотни файлов и превышают устойчивую производительность жесткого диска.Приложения для просмотра фотографий, такие как iPhoto или Aperture, по-видимому, открывают несколько файлов и отображают их, поэтому я предполагаю, что это можно сделать.

Я предполагаю, что самым большим ограничением будет дисковый ввод-вывод, поскольку приложениеможет (теоретически) читать и записывать несколько файлов одновременно.

Есть предложения?

TIA

Ответы [ 5 ]

7 голосов
/ 26 декабря 2010

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

Таким образом, вам необходимо немного соблюсти баланс.Скорее всего, один файл не самый эффективный, как вы заметили.

Лично?

Я бы использовал семафор рассылки.

Что-то вроде:

@property(nonatomic, assign) dispatch_queue_t dataQueue;
@property(nonatomic, assign) dispatch_semaphore_t execSemaphore;

И:

- (void) process:(NSData *)d {
    dispatch_async(self.dataQueue, ^{
        if (!dispatch_semaphore_wait(self.execSemaphore, DISPATCH_TIME_FOREVER)) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                ... do calcualtion work here on d ...
                dispatch_async(dispatch_get_main_queue(), ^{
                    .... update main thread w/new data here ....
                });
                dispatch_semaphore_signal(self.execSemaphore);
            });
        }
    });
}

Где оно начинается с:

self.dataQueue = dispatch_queue_create("com.yourcompany.dataqueue", NULL);
self.execSemaphore = dispatch_semaphore_create(3);
[self process: ...];
[self process: ...];
[self process: ...];
[self process: ...];
[self process: ...];
.... etc ....

Вам понадобитсяопределить, как лучше всего вы хотите справиться с очередями.Если есть много предметов и есть понятие отмены, постановка в очередь все, вероятно, расточительно.Точно так же вы, вероятно, захотите поставить в очередь URL-адреса для обрабатываемых файлов, а не объекты NSData, как указано выше.

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

6 голосов
/ 27 декабря 2010

Вы уже получили отличные ответы, но я хотел добавить пару баллов.Я работал над проектами, которые перечисляют все файлы в файловой системе и вычисляют MD5 и SHA1-хэши каждого файла (в дополнение к другой обработке).Если вы делаете что-то подобное, когда вы ищете большое количество файлов, и файлы могут иметь произвольное содержимое, то следует учитывать следующие моменты:

  • Как уже было отмечено, вы будете I /О связаны.Если вы одновременно прочитаете более 1 файла, это отрицательно скажется на производительности каждого расчета.Очевидно, что целью параллельного планирования вычислений является занятость диска между файлами, но вы можете рассмотреть возможность структурирования своей работы по-другому.Например, настройте один поток, который перечисляет и открывает файлы, и второй поток, который получает открытые файлы, обрабатывает файлы из первого потока по одному и обрабатывает их.Файловая система будет кэшировать информацию каталога, поэтому перечисление не окажет серьезного влияния на чтение данных, которые фактически должны будут попасть на диск.

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

  • Если вынет никакого другого использования для данных, кроме вычисления хэша, тогда я предлагаю отключить кэширование файловой системы перед чтением данных.

Если вы используете NSFileHandles, простой метод категории сделает это для каждого файла:

@interface NSFileHandle (NSFileHandleCaching)
- (BOOL)disableFileSystemCache;
@end

#include <fcntl.h>

@implementation NSFileHandle (NSFileHandleCaching)
- (BOOL)disableFileSystemCache {
     return (fcntl([self fileDescriptor], F_NOCACHE, 1) != -1);
}
@end
  • Если файлы коляски маленькие, выможет потребоваться собрать их в памяти и записать их партиями, чтобы минимизировать прерывание обработки.

  • Файловая система (по крайней мере, HFS) хранит записи файлов для файлов в каталоге последовательно, поэтому просмотрите файловую систему в ширину (т. е. обработайте каждый файл в каталоге, прежде чем вводить подкаталоги).

Выше, конечно, просто предложения.Вы захотите поэкспериментировать и измерить производительность, чтобы подтвердить фактическое влияние.

6 голосов
/ 26 декабря 2010

Я бы использовал NSOperation для этого из-за простоты обработки как зависимостей, так и отмены.

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

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

Причиной ограниченной ширины в очереди ввода / вывода является то, что ваша работа, скорее всего, будет связана с вводом / выводом; вы можете захотеть, чтобы он имел ширину больше 1, но вполне вероятно, что он напрямую связан с количеством физических дисков, на которых находятся ваши входные файлы. (Возможно, что-то вроде 2х, вы захотите определить это экспериментально.)

Код будет выглядеть примерно так:

@implementation FileProcessor

static NSOperationQueue *FileProcessorIOQueue = nil;
static NSOperationQueue *FileProcessorComputeQueue = nil;

+ (void)inititalize
{
    if (self == [FileProcessor class]) {
        FileProcessorIOQueue = [[NSOperationQueue alloc] init];
        [FileProcessorIOQueue setName:@"FileProcessorIOQueue"];
        [FileProcessorIOQueue setMaxConcurrentOperationCount:2]; // limit width

        FileProcessorComputeQueue = [[NSOperationQueue alloc] init];
        [FileProcessorComputeQueue setName:@"FileProcessorComputeQueue"];
    }
}

- (void)processFilesAtURLs:(NSArray *)URLs
{
    for (NSURL *URL in URLs) {
        __block NSData *fileData = nil; // set by readOperation
        __block NSData *fileHashData = nil; // set by computeOperation

        // Create operations to do the work for this URL

        NSBlockOperation *readOperation =
            [NSBlockOperation blockOperationWithBlock:^{
                fileData = CreateDataFromFileAtURL(URL);
            }];

        NSBlockOperation *computeOperation =
            [NSBlockOperation blockOperationWithBlock:^{
                fileHashData = CreateHashFromData(fileData);
                [fileData release]; // created in readOperation
            }];

        NSBlockOperation *writeOperation =
            [NSBlockOperation blockOperationWithBlock:^{
                WriteHashSidecarForFileAtURL(fileHashData, URL);
                [fileHashData release]; // created in computeOperation
            }];

        // Set up dependencies between operations

        [computeOperation addDependency:readOperation];
        [writeOperation addDependency:computeOperation];

        // Add operations to appropriate queues

        [FileProcessorIOQueue addOperation:readOperation];
        [FileProcessorComputeQueue addOperation:computeOperation];
        [FileProcessorIOQueue addOperation:writeOperation];
    }
}

@end

Это довольно просто; Вместо того чтобы работать с несколькими вложенными слоями синхронизации / асинхронности, как это было бы с API dispatch_*, NSOperation позволяет вам определять ваши единицы работы и ваши зависимости между ними независимо. В некоторых ситуациях это легче понять и отладить.

2 голосов
/ 17 сентября 2012

libdispatch фактически предоставляет API для этого!Проверьте dispatch_io;он будет обрабатывать распараллеливание ввода-вывода, когда это уместно, и, в противном случае, сериализовать его, чтобы избежать перегрузки диска.

1 голос
/ 03 сентября 2012

Следующая ссылка на проект BitBucket, который я настроил с использованием NSOperation и Grand Central Dispatch, в котором используется простое приложение для обеспечения целостности файлов.

https://bitbucket.org/torresj/hashar-cocoa

Я надеюсь, что это поможет / поможет.

...