Стратегия Grand Central для открытия нескольких файлов

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

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

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

Какие-либо предложения?

ТИА


person Torjt    schedule 26.12.2010    source источник
comment
Кен (codeweavers) прислал мне информативную информацию через список рассылки разработчиков Apple Cocoa. Вот его ответ: см. эту статью в блоге Майка Эша: mikeash.com/pyblog/friday-qa-2009-09-25-gcd-practicum.html Обсуждение в этой ветке также может быть вам полезно; хотя речь идет о NSOperationQueue, некоторые из тех же проблем относятся и к GCD: mail-archive.com/[email protected]/msg64583.html С уважением, Кен   -  person Torjt    schedule 29.12.2010


Ответы (5)


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

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

Лично?

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

Что-то типа:

@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, как указано выше.

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

person bbum    schedule 26.12.2010
comment
И, конечно же, будьте осторожны, так как это становится более сложным... в этом примере довольно много потоков. Легко заблокировать или повредить данные, если вы не внимательны к деталям! - person bbum; 26.12.2010
comment
bum, спасибо за ответ. Я попытаюсь включить файл dispatch_semaphore_t. Когда я закончу, я опубликую часть кода. Спасибо! - person Torjt; 27.12.2010

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

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

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

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

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

@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

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

person Chris Hanson    schedule 26.12.2010
comment
Крис, большое спасибо за подробный ответ. Я выбрал Grand Central Dispatch только потому, что хотел с ним поэкспериментировать. Я все же попробую ваш пример. - person Torjt; 27.12.2010
comment
Привет, Крис, у меня есть несколько вопросов: 1) При использовании NSOperation в Mac OS X (не iOS) будет ли также использоваться диспетчеризация Grand Central? 2) Если цель развертывания написанного вами кода предназначена для устройств до iOS 4.0 (где блоки не включены), будет ли NSBlockOperations автоматически преобразована в NSOperation, или нам нужно проверить версию ОС, прежде чем делать это? - person Enrico Susatyo; 10.01.2011
comment
Новые вопросы лучше задавать сами по себе, а не в комментариях. Тем не менее, NSOperation также использует GCD в Mac OS X. (Мой ответ не относится к iOS.) Кроме того, вы, как правило, не можете использовать язык или функцию ОС в ОС, предшествующей той, в которой они были представлены, независимо от того, что это за функция. - person Chris Hanson; 11.01.2011

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

  • Как уже отмечалось, вы будете привязаны к вводу-выводу. Если вы читаете более 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
  • Если файлы sidecar небольшие, вы можете собрать их в памяти и записать их пакетами, чтобы свести к минимуму прерывание обработки.

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

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

person Aaron Burghardt    schedule 27.12.2010
comment
Аарон, спасибо, что рассказал об отключении кеша файловой системы. Действительно, я использую данные только для вычисления хеша; это также должно помочь с производительностью. Спасибо! - person Torjt; 29.12.2010

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

person Catfish_Man    schedule 16.09.2012

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

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

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

person Torjt    schedule 02.09.2012