GCD Плохая производительность - PullRequest
9 голосов
/ 21 февраля 2011

Как вы помните, я пытаюсь использовать GCD для ускорения части моего кода, а именно механизма обнаружения и разрешения столкновений. Тем не менее, я явно делаю что-то не так, потому что весь мой код GCD значительно медленнее и менее последовательный, чем мой последовательный код (в 1,4–10 раз медленнее). Позвольте мне привести вам пример: я перебираю массив в виде пузырьков, чтобы определить все возможные коллизии между объектами в этом массиве:

- (double) detectCollisionsInArray:(NSArray*)objects
{   
    int count = [objects count];
    if (count > 0)
    {       
        double time = CFAbsoluteTimeGetCurrent();
        for (int i = 0; i < count; i++)
        {
            for (int j = i + 1; j < count; j++)
            {
                /** LOTS AND LOTS OF WORK FOR EACH OBJECT **/
            }
        }

        return CFAbsoluteTimeGetCurrent() - time;
    }

    return 0;
}

Довольно просто, и, похоже, хорошо работает, учитывая ограничения проблемы. Однако я хотел бы воспользоваться тем, что состояние каждого объекта не изменяется в разделе кода, и использовать GCD для распараллеливания этой работы. Для этого я пытаюсь что-то вроде этого:

- (double) detectCollisionsInArray:(NSArray*)objects
{   
    int count = [objects count];
    if (count > 0)
    {
        NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
        NSBlockOperation* blockOperation = nil;

        double time = CFAbsoluteTimeGetCurrent();
        for (int i = 0; i < count; i++)
        {
            for (int j = i + 1; j < count; j++)
            {
                void (^workBlock) (void) = ^() 
                {
                    /** LOTS AND LOTS OF WORK FOR EACH OBJECT **/
                };

                if (!blockOperation)
                {
                    blockOperation = [NSBlockOperation blockOperationWithBlock:b];
                }
                else
                {
                    [blockOperation addExecutionBlock:workBlock];
                }
            }
        }

        [opQueue addOperation:blockOperation];
        [opQueue autorelease];

        return CFAbsoluteTimeGetCurrent() - time;
    }

    return 0;
}

Может кто-нибудь помочь поставить меня на правильный путь и, возможно, дать ссылку на хороший учебник GCD? Я просмотрел несколько учебных пособий по GCD и изучил всю документацию, и я все еще чувствую, что мое понимание предмета в лучшем случае незначительно. Спасибо!

Ответы [ 2 ]

30 голосов
/ 22 февраля 2011

Есть ли причина, по которой вы не используете GCD C API и семейство функций dispatch_*? У вас нет большого контроля над GCD-аспектами NSOperationQueue (например, в какую очередь вы хотите отправить блоки). Кроме того, я не могу сказать, используете ли вы iOS или нет, но NSOperationQueue не не использует GCD на iOS. Это может быть причиной того, что он породил так много потоков. В любом случае ваш код будет короче и проще, если вы будете использовать GCD API напрямую:

- (double) detectCollisionsInArray:(NSArray*)objects
{   
  int count = [objects count];
  if (count > 0)
  {
    double time = CFAbsoluteTimeGetCurrent();

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < count; i++)
    {
      dispatch_group_async(group, queue, ^{
        for (int j = i + 1; j < count; j++)
        {
          dispatch_group_async(group, queue, ^{
            /** LOTS AND LOTS OF WORK FOR EACH OBJECT **/
          });
        }
      });
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_release(group);
    return CFAbsoluteTimeGetCurrent() - time;
  }
  return 0;
}

Вы можете использовать dispatch_group, чтобы сгруппировать все исполнения вместе и подождать, пока все они завершатся с dispatch_group_wait. Если вам не важно знать, когда заканчиваются блоки, вы можете игнорировать групповую часть и просто использовать dispatch_async. Функция dispatch_get_global_queue получит одну из 3 одновременных очередей (низкая, по умолчанию или с высоким приоритетом), в которую вы можете отправить свои блоки. Вам не нужно беспокоиться об ограничении числа потоков или чего-то подобного. Планировщик GCD должен сделать все это за вас. Просто убедитесь, что вы отправляете в параллельную очередь, которая может быть либо одной из 3 глобальных очередей, либо очередью, которую вы создали, передавая DISPATCH_QUEUE_CONCURRENT в dispatch_queue_create (это доступно начиная с OS X 10.7 и iOS 5.0).

Если вы выполняете какой-либо файловый ввод / вывод в каждом блоке или облагаете налогом какой-либо другой ресурс, вам, возможно, потребуется править в GCD и ограничить количество блоков, которые вы одновременно отправляете в очередь. Это будет иметь тот же эффект, что и ограничение числа одновременных операций в NSOperationQueue. Для этого вы можете использовать семафор GCD:

- (double) detectCollisionsInArray:(NSArray*)objects
{   
  int count = [objects count];
  if (count > 0)
  {
    double time = CFAbsoluteTimeGetCurrent();

    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < count; i++)
    {
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
      dispatch_group_async(group, queue, ^{
        for (int j = i + 1; j < count; j++)
        {
          dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
          dispatch_group_async(group, queue, ^{
            /** LOTS AND LOTS OF WORK FOR EACH OBJECT **/
            dispatch_semaphore_signal(semaphore);
          });
        }
        dispatch_semaphore_signal(semaphore);
      });
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_release(group);
    dispatch_release(semaphore);
    return CFAbsoluteTimeGetCurrent() - time;
  }
  return 0;
}

Как только вы освоитесь, GCD очень прост в использовании. Теперь я использую все это в своем коде.

Может кто-нибудь помочь поставить меня на правильный путь и, возможно, дать ссылку на хороший учебник по GCD?

Беги , не подходи к блогу Майка Эша . Его серия о GCD - самая ясная и краткая из всех, что я видел, и вам понадобится всего около 30 минут, чтобы прочитать все это. Видео Apple WWDC 2010 года на GCD И блоки тоже довольно хороши.

4 голосов
/ 21 февраля 2011

В вашем коде вы откладываете работу, которую вы должны выполнить для каждого объекта, до конца вложенного цикла for. Тем не менее, когда цикл завершится, у вас будет одна операция с большим количеством блоков для группы объектов, и вы не сможете должным образом использовать GCD.

Я бы предложил вам создать один NSBlockOperation для каждого объекта и добавить его к NSOperationQueue в конце каждой for (int j = i + 1; j < count; j++) итерации.

Таким образом, система начнет обрабатывать работу, которую вы должны выполнить для каждого объекта, как только закончится итерация.

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

...